在c#中随机化泛型列表顺序的最佳方法是什么?我在一个列表中有一个有限的75个数字集,我想随机分配一个顺序,以便为彩票类型的应用程序绘制它们。


当前回答

这里是Fisher-Yates shuffle的实现,允许指定返回的元素数量;因此,在获取所需数量的元素之前,没有必要首先对整个集合进行排序。

交换元素的顺序与默认值相反;从第一个元素到最后一个元素,因此检索集合的一个子集与洗牌整个集合产生相同的(部分)序列:

collection.TakeRandom(5).SequenceEqual(collection.Shuffle().Take(5)); // true

该算法基于Durstenfeld在维基百科上的(现代)Fisher-Yates shuffle。

public static IList<T> TakeRandom<T>(this IEnumerable<T> collection, int count, Random random) => shuffle(collection, count, random);
public static IList<T> Shuffle<T>(this IEnumerable<T> collection, Random random) => shuffle(collection, null, random);
private static IList<T> shuffle<T>(IEnumerable<T> collection, int? take, Random random)
{
    var a = collection.ToArray();
    var n = a.Length;
    if (take <= 0 || take > n) throw new ArgumentException("Invalid number of elements to return.");
    var end = take ?? n;
    for (int i = 0; i < end; i++)
    {
        var j = random.Next(i, n);
        (a[i], a[j]) = (a[j], a[i]);
    }

    if (take.HasValue) return new ArraySegment<T>(a, 0, take.Value);
    return a;
}

其他回答

    List<T> OriginalList = new List<T>();
    List<T> TempList = new List<T>();
    Random random = new Random();
    int length = OriginalList.Count;
    int TempIndex = 0;

    while (length > 0) {
        TempIndex = random.Next(0, length);  // get random value between 0 and original length
        TempList.Add(OriginalList[TempIndex]); // add to temp list
        OriginalList.RemoveAt(TempIndex); // remove from original list
        length = OriginalList.Count;  // get new list <T> length.
    }

    OriginalList = new List<T>();
    OriginalList = TempList; // copy all items from temp list to original list.

对已接受答案的简单修改,返回一个新的列表,而不是原地工作,并像许多其他Linq方法一样接受更通用的IEnumerable<T>。

private static Random rng = new Random();

/// <summary>
/// Returns a new list where the elements are randomly shuffled.
/// Based on the Fisher-Yates shuffle, which has O(n) complexity.
/// </summary>
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> list) {
    var source = list.ToList();
    int n = source.Count;
    var shuffled = new List<T>(n);
    shuffled.AddRange(source);
    while (n > 1) {
        n--;
        int k = rng.Next(n + 1);
        T value = shuffled[k];
        shuffled[k] = shuffled[n];
        shuffled[n] = value;
    }
    return shuffled;
}

这里是Fisher-Yates shuffle的实现,允许指定返回的元素数量;因此,在获取所需数量的元素之前,没有必要首先对整个集合进行排序。

交换元素的顺序与默认值相反;从第一个元素到最后一个元素,因此检索集合的一个子集与洗牌整个集合产生相同的(部分)序列:

collection.TakeRandom(5).SequenceEqual(collection.Shuffle().Take(5)); // true

该算法基于Durstenfeld在维基百科上的(现代)Fisher-Yates shuffle。

public static IList<T> TakeRandom<T>(this IEnumerable<T> collection, int count, Random random) => shuffle(collection, count, random);
public static IList<T> Shuffle<T>(this IEnumerable<T> collection, Random random) => shuffle(collection, null, random);
private static IList<T> shuffle<T>(IEnumerable<T> collection, int? take, Random random)
{
    var a = collection.ToArray();
    var n = a.Length;
    if (take <= 0 || take > n) throw new ArgumentException("Invalid number of elements to return.");
    var end = take ?? n;
    for (int i = 0; i < end; i++)
    {
        var j = random.Next(i, n);
        (a[i], a[j]) = (a[j], a[i]);
    }

    if (take.HasValue) return new ArraySegment<T>(a, 0, take.Value);
    return a;
}

通过使用元组进行交换,可以使Fisher-Yates shuffle更加简洁和富有表现力。

private static readonly Random random = new Random();

public static void Shuffle<T>(this IList<T> list)
{
    int n = list.Count;
    while (n > 1)
    {
        n--;
        int k = random.Next(n + 1);
        (list[k], list[n]) = (list[n], list[k]);
    }
}

可以使用morelinq包中的Shuffle扩展方法,它适用于IEnumerables

安装包morelinq

using MoreLinq;
...    
var randomized = list.Shuffle();