在C#中随机化泛型列表顺序的最佳方法是什么?我想给一个随机顺序分配一个列表中的75个数字的有限集合,以便为彩票类型的应用程序绘制它们。
在C#中随机化泛型列表顺序的最佳方法是什么?我想给一个随机顺序分配一个列表中的75个数字的有限集合,以便为彩票类型的应用程序绘制它们。
Answers:
随机播放任何(I)List
基于该扩展方法费雪耶茨洗牌:
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;
}
}
用法:
List<Product> products = GetProducts();
products.Shuffle();
上面的代码使用备受批评的System.Random方法来选择交换候选。它速度很快,但不如应有的随机。如果您需要更好的随机性,请使用System.Security.Cryptography中的随机数生成器,如下所示:
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)上可以进行简单的比较。
编辑:自从几年前写下这个答案以来,许多人向我发表评论或写信给我,指出我的比较中的一个愚蠢的缺陷。他们当然是对的。如果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<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;
}
}
}
}
Random rng = new Random();
一个static
就可以解决比较职位中的问题。由于每个后续呼叫都将接续先前的呼叫,因此会产生最后的随机结果。
如果我们只需要按完全随机的顺序随机排列项目(只是为了混合列表中的项目),我更喜欢这个简单而有效的代码,按guid排序项目...
var shuffledcards = cards.OrderBy(a => Guid.NewGuid()).ToList();
var shuffledcards = cards.OrderBy(a => rng.Next());
compilr.com/grenade/sandbox/Program.cs
NewGuid
仅保证它为您提供了唯一的GUID。它不能保证随机性。如果将GUID用于创建唯一值以外的目的,那么您做错了。
我对这个简单算法的所有笨拙版本感到惊讶。Fisher-Yates(或Knuth洗牌)有些棘手,但结构紧凑。为什么棘手?因为您需要注意您的随机数生成器r(a,b)
返回的值b
是“包含”还是“排除”。我还编辑了Wikipedia的描述,因此人们不会在盲目地遵循伪代码并造成难以检测的错误。对于.Net,不做任何事地Random.Next(a,b)
返回数字,b
因此,这里是如何在C#/。Net中实现的方法:
public static void Shuffle<T>(this IList<T> list, Random rnd)
{
for(var i=list.Count; i > 0; i--)
list.Swap(0, rnd.Next(0, i));
}
public static void Swap<T>(this IList<T> list, int i, int j)
{
var temp = list[i];
list[i] = list[j];
list[j] = temp;
}
i = list.Count - 1
,即最后一次迭代时,rnd.Next(i, list.Count)
我会回来的。因此i < list.Count -1
,您需要作为循环条件。);好了,你不“需要”,但它节省了1次迭代
IEnumerable的扩展方法:
public static IEnumerable<T> Randomize<T>(this IEnumerable<T> source)
{
Random rnd = new Random();
return source.OrderBy<T, int>((item) => rnd.Next());
}
OrderBy
使用QuickSort变体按(表面上随机的)键对项目进行排序。QuickSort性能为O(N log N);相反,Fisher-Yates混洗为O(N)。对于75个元素的集合,这可能没什么大不了的,但是对于较大的集合,这种差异将变得明显。
Random.Next()
可能会产生合理的伪随机值分布,但不能保证这些值是唯一的。当N达到2 ^ 32 + 1 时,重复密钥的概率随着N的增加(非线性)增长。该快速排序是一个稳定的排序; 因此,如果多个元素碰巧被分配了相同的伪随机索引值,则它们在输出序列中的顺序将与输入序列中的顺序相同;因此,在“混洗”中引入了偏差。OrderBy
想法是获取具有项目和随机顺序的匿名对象,然后按照此顺序对项目重新排序并返回值:
var result = items.Select(x => new { value = x, order = rnd.Next() })
.OrderBy(x => x.order).Select(x => x.value).ToList()
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;
}
var listCopy = list.ToList()
避免避免将所有项目从传入列表中弹出的事情?我真的不明白为什么您要将这些列表突变为空。
编辑
这RemoveAt
是我以前的版本中的一个弱点。该解决方案克服了这一点。
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];
}
}
请注意可选的Random generator
,如果的基本框架实现Random
不够线程安全或加密功能不足以满足您的需要,则可以将实现注入到操作中。
Random
可以在此答案中找到线程安全的密码学强实现的合适实现。
这是一个想法,以一种(希望)有效的方式扩展IList。
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]];
}
GetNext
还是Next
?
您可以使用此简单的扩展方法来实现
public static class IEnumerableExtensions
{
public static IEnumerable<t> Randomize<t>(this IEnumerable<t> target)
{
Random r = new Random();
return target.OrderBy(x=>(r.Next()));
}
}
您可以通过以下操作来使用它
// 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);
}
Random
类实例作为static
变量保留在函数之外。否则,如果快速连续调用,您可能会从计时器获得相同的随机种子。
当希望不修改原始图像时,这是我首选的随机播放方法。它是Fisher-Yates“由内而外”算法的一种变体,适用于任何可枚举的序列(source
不需要从头开始就知道其长度)。
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;
}
该算法还可以通过分配从0
到的范围来实现length - 1
并通过将随机选择的索引与最后一个索引交换直到所有索引都被精确选择一次随机耗尽索引。上面的代码完成了完全相同的事情,但是没有额外的分配。这很整洁。
关于Random
类,它是一个通用的数字生成器(如果我正在运行彩票,我会考虑使用其他方法)。默认情况下,它还依赖于基于时间的种子值。这个问题的一个小缓解方法是Random
使用RNGCryptoServiceProvider
或来为该类添加种子,或者您可以使用RNGCryptoServiceProvider
与此类似的方法(请参见下文)来生成统一选择的随机双浮点值,但是要运行彩票非常需要理解随机性和随机源。
var bytes = new byte[8];
_secureRng.GetBytes(bytes);
var v = BitConverter.ToUInt64(bytes, 0);
return (double)v / ((double)ulong.MaxValue + 1);
生成随机双精度数(仅在0和1之间)的目的是用于缩放为整数解。如果您需要从基于随机双精度数的列表中选择某个东西,x
那么总是0 <= x && x < 1
很简单的。
return list[(int)(x * list.Count)];
请享用!
如果您有固定的数字(75),则可以创建一个包含75个元素的数组,然后枚举列表,将元素移到数组中的随机位置。您可以使用Fisher-Yates shuffle生成列表编号到数组索引的映射。
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.
这是一个高效的Shuffler,它返回经过混洗的值的字节数组。它不会洗牌超过所需要的。可以从先前中断的位置重新启动。我的实际实现(未显示)是MEF组件,它允许用户指定替换的洗牌机。
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;
}
`
这是一种线程安全的方法:
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());
}
}
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);
}
对已接受答案的简单修改,它将返回一个新列表,而不是就地工作,并且接受IEnumerable<T>
许多其他Linq方法所做的更一般的操作。
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;
}
我在网上找到了一个有趣的解决方案。
礼貌:https : //improveandrepeat.com/2018/08/a-simple-way-to-shuffle-your-lists-in-c/
var shuffled = myList.OrderBy(x => Guid.NewGuid())。ToList();
可以肯定的是旧帖子,但是我只使用GUID。
Items = Items.OrderBy(o => Guid.NewGuid().ToString()).ToList();
GUID始终是唯一的,并且由于每次都会重新生成,因此结果每次都会更改。
解决此类问题的一种非常简单的方法是在列表中使用许多随机元素交换。
用伪代码看起来像这样:
do
r1 = randomPositionInList()
r2 = randomPositionInList()
swap elements at index r1 and index r2
for a certain number of times