简体   繁体   English

如何使用C#从列表中选择随机对象?

[英]How can I pick random objects out of a list with C#?

I have an IQueryable containing more than 300 objects: 我有一个包含超过300个对象的IQueryable:

public class Detail
{
    public int Id { get; set; }
    public int CityId { get; set; }
    public bool Chosen { get; set; }
}

IQueryable<Detail> details = ...

How can I go against this an at random pick out 50 objects? 我怎么能反对这个随机选择50个对象? I assume that I would need to convert this with .ToList() but I am not sure how I could pick out random elements. 我假设我需要使用.ToList()转换它,但我不确定如何挑选随机元素。

300 is not very much, so Yes, make it a List: 300不是很多,所以是的,使它成为一个列表:

IQueryable<Detail> details = ...
IList<Detail> detailList = details.ToList();

And now you can pick a random item : 现在你可以选择一个随机项目:

var randomItem = detailList[rand.Next(detailList.Count)];

and you could repeat that 50 times. 你可以重复50次。 That would however lead to duplicates, and the process to eliminate them would become messy. 然而,这将导致重复,并且消除它们的过程将变得混乱。

So use a standard shuffle algorithm and then pick the first 50 : 所以使用标准的shuffle算法 ,然后选择前50个:

Shuffle(detailList);
var selection = detailList.Take(50);

If you know in advance the total number of items from which to randomly pick, you can do it without converting to a list first. 如果您事先知道随机选择的项目总数,则可以在不先转换为列表的情况下进行。

The following method will do it for you: 以下方法将为您完成:

/// <summary>Randomly selects items from a sequence.</summary>
/// <typeparam name="T">The type of the items in the sequence.</typeparam>
/// <param name="sequence">The sequence from which to randomly select items.</param>
/// <param name="count">The number of items to randomly select from the sequence.</param>
/// <param name="sequenceLength">The number of items in the sequence among which to randomly select.</param>
/// <param name="rng">The random number generator to use.</param>
/// <returns>A sequence of randomly selected items.</returns>
/// <remarks>This is an O(N) algorithm (N is the sequence length).</remarks>

public static IEnumerable<T> RandomlySelectedItems<T>(IEnumerable<T> sequence, int count, int sequenceLength, System.Random rng)
{
    if (sequence == null)
    {
        throw new ArgumentNullException("sequence");
    }

    if (count < 0 || count > sequenceLength)
    {
        throw new ArgumentOutOfRangeException("count", count, "count must be between 0 and sequenceLength");
    }

    if (rng == null)
    {
        throw new ArgumentNullException("rng");
    }

    int available = sequenceLength;
    int remaining = count;
    var iterator  = sequence.GetEnumerator();

    for (int current = 0; current < sequenceLength; ++current)
    {
        iterator.MoveNext();

        if (rng.NextDouble() < remaining/(double)available)
        {
            yield return iterator.Current;
            --remaining;
        }

        --available;
    }
}

(The key thing here is needing to know at the start the number of items to choose from; this does reduce the utility somewhat. But if getting the count is quick and buffering all the items would take too much memory, this is a useful solution.) (这里的关键是需要在开始时知道可供选择的项目数量;这确实会减少实用程序。但是如果快速获取计数并且缓冲所有项目会占用太多内存,这是一个有用的解决方案。)


Here's another approach which uses Reservoir sampling 这是另一种使用油藏采样的方法

This approach DOES NOT need to know the total number of items to choose from, but it does need to buffer the output. 这种方法不需要知道可供选择的项目总数,但它确实需要缓冲输出。 Of course, it also needs to enumerate the entire input collection. 当然,它还需要枚举整个输入集合。

Therefore this is really only of use when you don't know in advance the number of items to choose from (or the number of items to choose from is very large). 因此,当您事先不知道可供选择的项目数量(或者可供选择的项目数量非常大)时,这实际上只是有用。

I would recommend just shuffling a list as per Henk's answer rather than doing it this way, but I'm including it here for the sake of interest: 我建议只按照Henk的答案洗牌,而不是这样做,但我为了感兴趣而把它包括在这里:

// n is the number of items to randomly choose.

public static List<T> RandomlyChooseItems<T>(IEnumerable<T> items, int n, Random rng)
{
    var result = new List<T>(n);
    int index = 0;

    foreach (var item in items)
    {
        if (index < n)
        {
            result.Add(item);
        }
        else
        {
            int r = rng.Next(0, index + 1);

            if (r < n)
                result[r] = item;
        }

        ++index;
    }

    return result;
}

As an addendum to Henk's answer, here's a canonical implementation of the Shuffle algorithm he mentions. 作为Henk答案的附录,这里是他提到的Shuffle算法的规范实现。 In this, _rng is an instance of Random : 在这里, _rngRandom一个实例:

/// <summary>Shuffles the specified array.</summary>
/// <typeparam name="T">The type of the array elements.</typeparam>
/// <param name="array">The array to shuffle.</param>

public void Shuffle<T>(IList<T> array)
{
    for (int n = array.Count; n > 1;)
    {
        int k = _rng.Next(n);
        --n;
        T temp = array[n];
        array[n] = array[k];
        array[k] = temp;
    }
}
Random rnd = new Random();
IQueryable<Detail> details = myList.OrderBy(x => rnd.Next()).Take(50);
IQueryable<Detail> details = myList.OrderBy(x => Guid.NewGuid()).ToList();

After this just walk trough it linearly: 在这之后,直接走线:

var item1 = details[0];

This will avoid duplicates. 这样可以避免重复。

var l = new List<string>();
l.Add("A");
l.Add("B");
l.Add("C");
l.Add("D");
l.Add("E");
l.Add("F");
l.Add("G");
l.Add("H");
l.Add("I");

var random = new Random();
var nl = l.Select(i=> new {Value=i,Index = random.Next()});

var finalList = nl.OrderBy(i=>i.Index).Take(3);
foreach(var i in finalList)
{
    Console.WriteLine(i.Value);
}

This is what ended up working for me, it ensures no duplicates are returned: 这最终为我工作,它确保没有返回重复:

public List<T> GetRandomItems(List<T> items, int count = 3)
{
    var length = items.Count();
    var list = new List<T>();
    var rnd = new Random();
    var seed = 0;

    while (list.Count() < count)
    {
        seed = rnd.Next(0, length);
        if(!list.Contains(items[seed]))
            list.Add(items[seed]);
    }

    return list;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM