Returning items randomly from a collection

旧城冷巷雨未停 提交于 2019-12-01 21:59:25

I wrote a TakeRandom extension method a while back which does this using a Fisher-Yates shuffle. It's pretty efficient as it only bothers to randomise the number of items that you actually want to return, and is guaranteed to be unbiased.

public static IEnumerable<T> TakeRandom<T>(this IEnumerable<T> source, int count)
{
    var array = source.ToArray();
    return ShuffleInternal(array, Math.Min(count, array.Length)).Take(count);
}

private static IEnumerable<T> ShuffleInternal<T>(T[] array, int count)
{
    for (var n = 0; n < count; n++)
    {
        var k = ThreadSafeRandom.Next(n, array.Length);
        var temp = array[n];
        array[n] = array[k];
        array[k] = temp;
    }

    return array;
}

An implementation of ThreadSafeRandom can be found at the PFX team blog.

Ruben Bartelink

You really should do this in the database - no point in returning a big stack of stuff only to drop all but five on the floor. You should amend your question to explain what typew of data access stack is involved so people can give better answers. For instance, you could do an ORDER BY RAND():

SELECT TOP 5 ... FROM orders
ORDER BY RAND()

But that visits every row, which you don't want. If you're using SQL Server [and would like to be tied to it :P], you could use TABLESAMPLE.

If you're using LINQ to SQL, go here

EDIT: Just pretend the rest of this isnt here - it's not efficient and hence Greg's answer is far more desirable if you do want to sort client-side.

But, for completeness, paste the following into LINQPad:

var orders = new[] { "a", "b", "c", "d", "e", "f" };
var random = new Random();
var result = Enumerable.Range(1,5).Select(i=>orders[random.Next(5)])
result.Dump();

EDIT: Brute force answer to Greg's point (Yes, not efficient or pretty)

var orders = new[] { "a", "b", "c", "d", "e", "f" };

var random = new Random();

int countToTake = 5;

var taken = new List<int>( countToTake);

var result = Enumerable.Range(1,countToTake)
    .Select(i=>{
        int itemToTake; 
        do { 
            itemToTake = random.Next(orders.Length); 
        } while (taken.Contains(itemToTake)); 
        taken.Add(itemToTake); 
        return orders[itemToTake];
    });

result.Dump();
return myList.OfType<Order>().OrderBy(o => Guid.NewGuid()).Take(5);
RA.
return collection.Where(()=>Random.Next(100) > (5 / collection.Count * 100)));
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!