在C#中随机化通用列表顺序的最佳方法是什么?我在一个列表中有一组有限的75个数字,我想为其分配一个随机顺序,以便为抽奖类型的应用程序绘制它们.
随机播放任何(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; } }
用法:
Listproducts = 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; } } } }
new Random() code>是问题,而不是 Random code>的随机性[解释](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()