3

我找到了这篇文章:

从链表中有效地选择一组随机元素

但这意味着,为了接近样本中的真正随机性,我必须遍历所有元素,用随机数将它们放入内存中,然后排序。我这里有很多项目(数百万) - 有没有更有效的方法来解决这个问题?

4

3 回答 3

11

我建议简单地改组元素,就好像您正在编写修改后的 Fisher-Yates 改组一样,但只打扰改组第一个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为参数,以便您可以重复使用相同的参数。不过要小心线程——有关更多信息,请参阅我关于随机性的文章。

于 2013-07-20T16:52:59.703 回答
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);
}
于 2013-07-20T15:37:24.723 回答
3

看看这个扩展方法http://extensionmethod.net/csharp/ienumerable-t/shuffle。您可以添加 Skip() Take() 类型以将值从最终列表中分页。

于 2013-07-20T15:22:15.890 回答