给定长度为n的列表,使用C#选择k个随机元素
本文关键字:选择 使用 元素 随机 列表 | 更新日期: 2023-09-27 18:24:06
我发现了这篇文章:
从链接列表中有效地选择一组随机元素
但这意味着,为了在样本中接近真正的随机性,我必须迭代所有元素,用随机数将它们放入内存,然后排序。我这里有一组非常大的项目(数百万)-有更有效的方法来解决这个问题吗?
我建议简单地打乱元素,就像你在写一个修改过的Fisher Yates shuffle一样,但只需要打乱第一个k
元素。例如:
public static void PartialShuffle<T>(IList<T> source, int count, Random random)
{
for (int i = 0; i < count; i++)
{
// Pick a random element out of the remaining elements,
// and swap it into place.
int index = i + random.Next(source.Count - i);
T tmp = source[index];
source[index] = source[i];
source[i] = tmp;
}
}
调用此方法后,第一个count
元素将是从原始列表中随机选取的元素。
请注意,我已经指定了Random
作为参数,这样您就可以重复使用同一个参数。不过,要小心线程——有关更多信息,请参阅我关于随机性的文章。
看看这个扩展方法http://extensionmethod.net/csharp/ienumerable-t/shuffle.您可以添加Skip()Take()类型,将值从最终列表中分页出来。
如果元素可以在内存中,请先将它们放入内存
List<Element> elements = dbContext.Select<Element>();
现在你知道了元素的数量。创建一组唯一索引。
var random = new Random();
var indexes = new HashSet<int>();
while (indexes.Count < k) {
indexes.Add(random.Next(elements.Count));
}
现在你可以从列表中读取元素
var randomElements = indexes.Select(i => elements[i]);
我假设DB包含唯一的元素。如果不是这种情况,您将不得不创建一个HashSet<Elements>
,或者在从DB查询时附加.Distinct()
。
更新
正如Patricia Shanahan所说,如果k与n相比很小,这种方法会很好地工作。如果不是这样,我建议选择一组n-k索引来排除
var random = new Random();
var indexes = new HashSet<int>();
IEnumerable<Element> randomElements;
if (k <= elements.Count / 2) {
while (indexes.Count < k) {
indexes.Add(random.Next(elements.Count));
}
randomElements = indexes.Select(i => elements[i]);
} else {
while (indexes.Count < elements.Count - k) {
indexes.Add(random.Next(elements.Count));
}
randomElements = elements
.Select((e,i) => indexes.Contains(i) ? null : elements[i])
.Where(e => e != null);
}