我找到了这篇文章:
Efficiently selecting a set of random elements from a linked list
但是这意味着为了在样本中接近真正的随机性,我必须迭代所有元素,用随机数将它们抛入内存,然后排序。我这里有一大堆物品(数百万) - 是否有更有效的方法解决这个问题?
答案 0 :(得分:11)
我建议简单地改变元素,好像你正在编写一个修改过的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
指定为参数,以便您可以重复使用相同的参数。但请注意线程化 - 请参阅我的article on randomness以获取更多信息。
答案 1 :(得分:3)
看看这个扩展方法http://extensionmethod.net/csharp/ienumerable-t/shuffle。您可以添加Skip()Take()类型以将值分页到最终列表中。
答案 2 :(得分:3)
如果元素可以在内存中,请先将它们放在内存中
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]);
我假设数据库包含唯一元素。如果不是这种情况,则必须改为创建HashSet<Elements>
,或者在从数据库查询时附加.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);
}