当前位置:  开发笔记 > 编程语言 > 正文

随机化List <T>

如何解决《随机化List<T>》经验,为你挑选了7个好方法。

在C#中随机化通用列表顺序的最佳方法是什么?我在一个列表中有一组有限的75个数字,我想为其分配一个随机顺序,以便为抽奖类型的应用程序绘制它们.



1> grenade..:

随机播放任何(I)List基于该扩展方法费雪耶茨洗牌:

private static Random rng = new Random();  

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

用法:

List products = GetProducts();
products.Shuffle();

上面的代码使用备受批评的System.Random方法来选择交换候选.它很快但不是随意的.如果你需要在shuffle中使用更好的随机性质,请使用System.Security.Cryptography中的随机数生成器,如下所示:

using System.Security.Cryptography;
...
public static void Shuffle(this IList list)
{
    RNGCryptoServiceProvider provider = new RNGCryptoServiceProvider();
    int n = list.Count;
    while (n > 1)
    {
        byte[] box = new byte[1];
        do provider.GetBytes(box);
        while (!(box[0] < n * (Byte.MaxValue / n)));
        int k = (box[0] % n);
        n--;
        T value = list[k];
        list[k] = list[n];
        list[n] = value;
    }
}

这个博客(WayBack Machine)提供了一个简单的比较.

编辑:自从几年前写这个答案以来,很多人都评论或写信给我,指出我比较中的一个很大的愚蠢缺陷.他们当然是对的.System.Random没有任何问题,如果按照预期的方式使用它.在我上面的第一个例子中,我在Shuffle方法中实例化了rng变量,如果要重复调用该方法,则会遇到麻烦.下面是一个固定的完整示例,基于今天从@weston收到的关于SO的真实有用的评论.

Program.cs中:

using System;
using System.Collections.Generic;
using System.Threading;

namespace SimpleLottery
{
  class Program
  {
    private static void Main(string[] args)
    {
      var numbers = new List(Enumerable.Range(1, 75));
      numbers.Shuffle();
      Console.WriteLine("The winning numbers are: {0}", string.Join(",  ", numbers.GetRange(0, 5)));
    }
  }

  public static class ThreadSafeRandom
  {
      [ThreadStatic] private static Random Local;

      public static Random ThisThreadsRandom
      {
          get { return Local ?? (Local = new Random(unchecked(Environment.TickCount * 31 + Thread.CurrentThread.ManagedThreadId))); }
      }
  }

  static class MyExtensions
  {
    public static void Shuffle(this IList list)
    {
      int n = list.Count;
      while (n > 1)
      {
        n--;
        int k = ThreadSafeRandom.ThisThreadsRandom.Next(n + 1);
        T value = list[k];
        list[k] = list[n];
        list[n] = value;
      }
    }
  }
}


如果list.Count是> Byte.MaxValue怎么办?如果n = 1000,则255/1000 = 0,因此do循环将是无限循环,因为box [0] <0始终为false.
我想指出,这种比较是有缺陷的.在循环中使用 new Random()是问题,而不是 Random 的随机性[解释](http://stackoverflow.com/questions/3053807/random-number -in-一个环/ 3053811#3053811)
将一个Random实例传递给Shuffle方法而不是在里面创建它是一个好主意,就好像你快速连续多次调用Shuffle一样(例如,拖拽大量的短列表),这些列表都将在同一个方面被洗牌方式(例如,第一项始终移动到位置3).
只需制作`Random rng = new Random();```static`就可以解决比较帖中的问题.因为每个后续呼叫将从先前的呼叫中继续进行最后的随机结果.
#2,目前尚不清楚带有加密生成器的版本是否有效,因为一个字节的最大范围是255,所以任何大于该值的列表都不会正确地进行混洗.
必须仔细检查PRNG中的熵位数.例如,要洗牌52张牌并且能够获得每个可能的洗牌牌,并以相同的概率进行,**你需要一个至少有226位种子的PRNG!**请不要使用当需要所有可能结果的相等概率时,这个答案中的方法可以改变事物,除非你做适当的计算机科学以确保它正常工作.
请注意,LinkedLists与ArrayLists的性能大幅降低.始终确保向其发送O(1)性能可索引数组.
你的比较文章有点遗漏了.`System.Random`没有你显示的那么糟糕.`new Random()`是[使用与时间相关的默认种子值](http://msdn.microsoft.com/en-us/library/h343ddh9.aspx)这就是为什么测试中根本没有随机化的原因.出于一个真正的原因,请参阅http://boallen.com/random-numbers.html,但这又有点依赖于操作系统,因此`System.Random`可能表现得更好,或者没有.
如果两个不同的线程同时使用具有共享RNG的版本,则RNG易于破坏其状态,因为它不是线程安全的(http://stackoverflow.com/a/11109361/155892).

2> 小智..:

如果我们只需要以完全随机的顺序对项目进行混洗(只是为了混合列表中的项目),我更喜欢这个简单而有效的代码,它通过guid命令项目...

var shuffledcards = cards.OrderBy(a => Guid.NewGuid()).ToList();


这是一个很好的优雅解决方案.如果你想要一个除了guid之外的东西来产生随机性,那就按照别的顺序排序吧.例如:`var shuffledcards = cards.OrderBy(a => rng.Next());`https://compilr.com/grenade/sandbox/Program.cs
@VitoDeTullio:你记错了.当您提供随机*比较函数*时,您会冒无限循环的风险; 需要比较函数来生成一致的*总订单*.随机*键*很好.这个建议是错误的,因为*guids不保证是随机的*,不是因为按随机键排序的技术是错误的.
GUID意味着独特而非随机.其中一部分是基于机器的,另一部分是基于时间的,只有一小部分是随机的.http://blogs.msdn.com/b/oldnewthing/archive/2008/06/27/8659071.aspx
@Doug:`NewGuid`只保证它为你提供一个独特的GUID.它不保证随机性.如果您将GUID用于创建*unique*值之外的其他目的,那么您做错了.
请不.这是错的."随意排序"完全不是一种洗牌:你引入了偏见,更糟糕的是,你冒险进入无限循环
它以优雅的方式完成工作.许多人只是希望他们的名单改组,而不关心它是多么随机.他们更倾向于使用这种方法而不是任何其他更具学术性的方法.我知道我这样做.谢谢!
这是一个很好的答案,但是我想警告使用它的人:如果您的列表很大,这不是一个好主意。.OrderBy(RandomFunc)的O(n)表示法是n * log(n),而不是n。对于1-1000个条目,这没什么大不了的。对于一百万个条目,它将慢100倍;对于十亿个条目,它将慢1000倍。

3> Shital Shah..:

我对这个简单算法的所有笨重版本感到有些惊讶.Fisher-Yates(或Knuth shuffle)有点棘手但非常紧凑.如果你去维基百科,你会看到这个算法的版本反向循环,很多人似乎并不真正理解为什么它会反过来.关键原因是这个版本的算法假定您使用的随机数生成器r(a,b)具有以下两个属性:

    它接受n作为单个输入参数.

    它从0返回数ñ 包容性.

但.Net随机数生成器不满足#2属性.的b,而不是返回数目从0到n-1以下.如果您尝试反向使用for循环,则需要调用Random.Next(a,b)哪个添加一个额外的操作.

但是,.Net随机数生成器还有另一个很好的函数b,它返回a到b-1.这实际上非常适合具有正常for循环的该算法的实现.所以,不用多说,这是正确,高效和紧凑的实现:

public static void Shuffle(this IList list, Random rnd)
{
    for(var i=list.Count; i > 0; i--)
        list.Swap(0, rnd.Next(0, i));
}

public static void Swap(this IList list, int i, int j)
{
    var temp = list[i];
    list[i] = list[j];
    list[j] = temp;
}


@Donuts - 没有.如果你这样做,你会在洗牌中增加偏见.
当`i = list.Count - 1`,即最后一次迭代时,`rnd.Next(i,list.Count)`会给你回复.因此,您需要`i 通过将Swap 分离为单独的方法,您似乎会为temp导致大量不必要的T分配.
我认为LINQ可能会降低混乱的性能,这也是不使用它的原因,特别是考虑到代码的相对简单性.

4> 小智..:

IEnumerable的扩展方法:

public static IEnumerable Randomize(this IEnumerable source)
{
    Random rnd = new Random();
    return source.OrderBy((item) => rnd.Next());
}


@JohnBeyer:存在远远超过偏见来源的问题.Random只有40亿种可能的种子,远远小于中等大小的可能的shuffle数量.只能生成一小部分可能的混洗.这种偏见使由于意外碰撞导致的偏差相形见绌.
... - `Random.Next()`可能产生一个合理的伪随机的值分布,但它确实不保证这些值是唯一的.重复密钥的概率随_N_增长(非线性),直到_N_达到2 ^ 32 + 1时达到确定性.`OrderBy`QuickSort是_stable_排序; 因此,如果多个元素碰巧被赋予相同的伪随机索引值,那么它们在输出序列中的顺序将是_same_和输入序列中的顺序; 因此,偏差被引入"洗牌".
该算法存在两个重要问题: - "OrderBy"使用QuickSort变体按其(表面上是随机的)键对项目进行排序.QuickSort性能为_O(N log N)_; 相比之下,Fisher-Yates shuffle是_O(N)_.对于75个元素的集合,这可能不是什么大问题,但是对于更大的集合来说差异将变得明显.
请注意,这不是线程安全的,即使在线程安全列表中使用也是如此

5> Adam Tegen..:
    public static List Randomize(List list)
    {
        List randomizedList = new List();
        Random rnd = new Random();
        while (list.Count > 0)
        {
            int index = rnd.Next(0, list.Count); //pick a random item from the master list
            randomizedList.Add(list[index]); //place it at the end of the randomized list
            list.RemoveAt(index);
        }
        return randomizedList;
    }


你不应该像`var listCopy = list.ToList()`这样做,以避免弹出传入列表中的所有项目吗?我真的不明白为什么你想要将这些列表变为空.

6> Jodrell..:

编辑RemoveAt是我以前版本的一个弱点.该解决方案克服了这一点.

public static IEnumerable Shuffle(
        this IEnumerable source,
        Random generator = null)
{
    if (generator == null)
    {
        generator = new Random();
    }

    var elements = source.ToArray();
    for (var i = elements.Length - 1; i >= 0; i--)
    {
        var swapIndex = generator.Next(i + 1);
        yield return elements[swapIndex];
        elements[swapIndex] = elements[i];
    }
}

注意可选的Random generator,如果基本框架实现Random不是线程安全的或加密强度足以满足您的需要,您可以将您的实现注入操作.

Random在这个答案中可以找到适合线程安全的加密强实现的实现.


这是一个想法,以(希望)有效的方式扩展IList.

public static IEnumerable Shuffle(this IList list)
{
    var choices = Enumerable.Range(0, list.Count).ToList();
    var rng = new Random();
    for(int n = choices.Count; n > 1; n--)
    {
        int k = rng.Next(n);
        yield return list[choices[k]];
        choices.RemoveAt(k);
    }

    yield return list[choices[0]];
}



7> 小智..:

想法是获得具有项目和随机顺序的匿名对象,然后按照此顺序对项目重新排序并返回值:

var result = items.Select(x => new { value = x, order = rnd.Next() })
            .OrderBy(x => x.order).Select(x => x.value).ToList()

推荐阅读
有风吹过best
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有