给定长度列表n使用C#选择k个随机元素

时间:2013-07-20 15:17:08

标签: c# .net algorithm

我找到了这篇文章:

Efficiently selecting a set of random elements from a linked list

但是这意味着为了在样本中接近真正的随机性,我必须迭代所有元素,用随机数将它们抛入内存,然后排序。我这里有一大堆物品(数百万) - 是否有更有效的方法解决这个问题?

3 个答案:

答案 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);
}