Randomize a List<T>
在C中随机化通用列表顺序的最佳方法是什么?我在一个列表中有一个75个数字的有限集合,我想给它们分配一个随机顺序,以便为彩票类型的应用程序绘制它们。
使用基于fisher-yates shuffle的扩展方法对任何
1 2 3 4 5 6 7 8 9 10 11 12 13 | private static Random rng = new Random(); public static void Shuffle<T>(this IList<T> 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; } } |
用途:
1 2 | List<Product> products = GetProducts(); products.Shuffle(); |
上面的代码使用了备受批评的系统随机方法来选择交换候选对象。它很快,但并不像应该的那么随机。如果您在随机移动中需要更好的随机性质量,请使用System.Security.Cryptography中的随机数生成器,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | using System.Security.Cryptography; ... public static void Shuffle<T>(this IList<T> 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)提供了一个简单的比较。
编辑:自从几年前写下这个答案以来,很多人评论或写信给我,指出了我比较中的一个巨大的愚蠢缺陷。他们当然是对的。系统没有任何问题。如果按照预期的方式使用,则是随机的。在上面的第一个示例中,我实例化了shuffle方法内部的rng变量,如果要重复调用该方法,则会出现问题。下面是一个固定的完整例子,基于今天从@weston这里收到的一个非常有用的评论。
Program.cs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | using System; using System.Collections.Generic; using System.Threading; namespace SimpleLottery { class Program { private static void Main(string[] args) { var numbers = new List<int>(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<T>(this IList<T> 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; } } } } |
如果我们只需要以完全随机的顺序无序排列项目(只是为了混合列表中的项目),我更喜欢这个简单而有效的代码,它按guid排序项目…
1 | var shuffledcards = cards.OrderBy(a => Guid.NewGuid()).ToList(); |
我有点惊讶于这个简单算法的所有笨拙版本。费希尔·耶茨(或克努斯洗牌)有点棘手,但非常紧凑。如果你去维基百科,你会看到这个算法的一个版本,它的for循环是相反的,而且很多人似乎并不真正理解为什么它是相反的。关键原因是,此版本的算法假定随机数生成器
但是.NET随机数生成器不满足2属性。相反,
然而,.NET随机数生成器还有一个很好的函数
1 2 3 4 5 6 7 8 9 10 11 12 | public static void Shuffle<T>(this IList<T> list, Random rnd) { for(var i=0; i < list.Count - 1; i++) list.Swap(i, rnd.Next(i, list.Count)); } public static void Swap<T>(this IList<T> list, int i, int j) { var temp = list[i]; list[i] = list[j]; list[j] = temp; } |
IEnumerable的扩展方法:
1 2 3 4 5 | public static IEnumerable<T> Randomize<T>(this IEnumerable<T> source) { Random rnd = new Random(); return source.OrderBy<T, int>((item) => rnd.Next()); } |
1 2 3 4 5 6 7 8 9 10 11 12 | public static List<T> Randomize<T>(List<T> list) { List<T> randomizedList = new List<T>(); 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; } |
编辑在我以前的版本中,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public static IEnumerable<T> Shuffle<T>( this IEnumerable<T> 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]; } } |
注意可选的
在这个答案中可以找到一个线程安全的、加密性很强的
这里有一个想法,以(希望)有效的方式扩展ilist。
1 2 3 4 5 6 7 8 9 10 11 12 13 | public static IEnumerable<T> Shuffle<T>(this IList<T> 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]]; } |
您可以使用这个简单的扩展方法来实现这一点。
1 2 3 4 5 6 7 8 9 10 | public static class IEnumerableExtensions { public static IEnumerable<t> Randomize<t>(this IEnumerable<t> target) { Random r = new Random(); return target.OrderBy(x=>(r.Next())); } } |
您可以通过以下操作来使用它
1 2 3 4 5 6 7 8 9 | // use this on any collection that implements IEnumerable! // List, Array, HashSet, Collection, etc List<string> myList = new List<string> {"hello","random","world","foo","bar","bat","baz" }; foreach (string s in myList.Randomize()) { Console.WriteLine(s); } |
我通常使用:
1 2 3 4 5 6 7 8 9 10 |
这是我首选的洗牌方法,当它是不希望修改原始的。它是Fisher-Yates"由内向外"算法的一个变体,适用于任何可枚举序列(从一开始就不需要知道EDOCX1[0]的长度)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public static IList<T> NextList<T>(this Random r, IEnumerable<T> source) { var list = new List<T>(); foreach (var item in source) { var i = r.Next(list.Count + 1); if (i == list.Count) { list.Add(item); } else { var temp = list[i]; list[i] = item; list.Add(temp); } } return list; } |
该算法还可以通过分配从
关于
1 2 3 4 | var bytes = new byte[8]; _secureRng.GetBytes(bytes); var v = BitConverter.ToUInt64(bytes, 0); return (double)v / ((double)ulong.MaxValue + 1); |
生成一个随机双精度(0到1之间的唯一值)的点是用来缩放到整数解。如果你需要从一个基于随机双
1 | return list[(int)(x * list.Count)]; |
享受!
其思想是用项目和随机顺序获取一个灵活的对象,然后按此顺序重新排序项目并返回值:
1 2 | var result = items.Select(x => new { value = x, order = rnd.Next() }) .OrderBy(x => x.order).Select(x => x.value).ToList() |
如果您有一个固定的数字(75),您可以创建一个包含75个元素的数组,然后枚举列表,将元素移动到数组中的随机位置。您可以使用fisher-yates shuffle生成列表编号到数组索引的映射。
如果您不介意使用两个
1 2 3 4 5 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | public Deck(IEnumerable<Card> initialCards) { cards = new List<Card>(initialCards); public void Shuffle() } { List<Card> NewCards = new List<Card>(); while (cards.Count > 0) { int CardToMove = random.Next(cards.Count); NewCards.Add(cards[CardToMove]); cards.RemoveAt(CardToMove); } cards = NewCards; } public IEnumerable<string> GetCardNames() { string[] CardNames = new string[cards.Count]; for (int i = 0; i < cards.Count; i++) CardNames[i] = cards[i].Name; return CardNames; } Deck deck1; Deck deck2; Random random = new Random(); public Form1() { InitializeComponent(); ResetDeck(1); ResetDeck(2); RedrawDeck(1); RedrawDeck(2); } private void ResetDeck(int deckNumber) { if (deckNumber == 1) { int numberOfCards = random.Next(1, 11); deck1 = new Deck(new Card[] { }); for (int i = 0; i < numberOfCards; i++) deck1.Add(new Card((Suits)random.Next(4),(Values)random.Next(1, 14))); deck1.Sort(); } else deck2 = new Deck(); } private void reset1_Click(object sender, EventArgs e) { ResetDeck(1); RedrawDeck(1); } private void shuffle1_Click(object sender, EventArgs e) { deck1.Shuffle(); RedrawDeck(1); } private void moveToDeck1_Click(object sender, EventArgs e) { if (listBox2.SelectedIndex >= 0) if (deck2.Count > 0) { deck1.Add(deck2.Deal(listBox2.SelectedIndex)); } RedrawDeck(1); RedrawDeck(2); } |
下面是一个线程安全的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | public static class EnumerableExtension { private static Random globalRng = new Random(); [ThreadStatic] private static Random _rng; private static Random rng { get { if (_rng == null) { int seed; lock (globalRng) { seed = globalRng.Next(); } _rng = new Random(seed); } return _rng; } } public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> items) { return items.OrderBy (i => rng.Next()); } } |
对已接受回答的一种简单修改,它返回一个新的列表,而不是原地工作,并像许多其他LINQ方法那样接受更一般的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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; } |
这是一个高效的随机播放程序,它返回一个随机播放值的字节数组。它的洗牌次数永远不会超过需要的次数。它可以从以前停止的地方重新启动。我的实际实现(未显示)是一个MEF组件,它允许用户指定的替换洗牌器。
1 2 3 4 5 6 7 8 9 10 11 12 13 | public byte[] Shuffle(byte[] array, int start, int count) { int n = array.Length - start; byte[] shuffled = new byte[count]; for(int i = 0; i < count; i++, start++) { int k = UniformRandomGenerator.Next(n--) + start; shuffled[i] = array[k]; array[k] = array[start]; array[start] = shuffled[i]; } return shuffled; } |
`
当然是旧的帖子,但我只是使用一个guid。
1 | Items = Items.OrderBy(o => Guid.NewGuid().ToString()).ToList(); |
guid总是唯一的,因为每次结果更改时都会重新生成guid。
解决这类问题的一个非常简单的方法是在列表中使用一些随机元素交换。
在伪代码中,如下所示:
1 2 3 4 5 | do r1 = randomPositionInList() r2 = randomPositionInList() swap elements at index r1 and index r2 for a certain number of times |