用.NET随机化字符串数组的最佳方法是什么?我的数组包含大约500个字符串,我想创建一个Array
具有相同字符串但随机顺序的新字符串。
请在答案中包含一个C#示例。
用.NET随机化字符串数组的最佳方法是什么?我的数组包含大约500个字符串,我想创建一个Array
具有相同字符串但随机顺序的新字符串。
请在答案中包含一个C#示例。
Answers:
如果您使用的是.NET 3.5,则可以使用以下IEnumerable凉爽性(VB.NET,而不是C#,但是思路应该很清楚...):
Random rnd=new Random();
string[] MyRandomArray = MyArray.OrderBy(x => rnd.Next()).ToArray();
编辑:确定,这是相应的VB.NET代码:
Dim rnd As New System.Random
Dim MyRandomArray = MyArray.OrderBy(Function() rnd.Next()).ToArray()
第二次编辑,以回应由于返回基于时间的序列而导致System.Random“不是线程安全的”并且“仅适用于玩具应用程序”:如在我的示例中所使用的,Random()完全是线程安全的,除非您允许重新输入随机数组的例程,在这种情况下,无论如何您都需要类似的东西lock (MyRandomArray)
以免破坏您的数据,这也将提供保护rnd
。
同样,应该很好地理解System.Random作为熵的来源不是很强大。如MSDN文档中所述,System.Security.Cryptography.RandomNumberGenerator
如果您要进行与安全性有关的任何操作,则应使用从中获取的信息。例如:
using System.Security.Cryptography;
...
RNGCryptoServiceProvider rnd = new RNGCryptoServiceProvider();
string[] MyRandomArray = MyArray.OrderBy(x => GetNextInt32(rnd)).ToArray();
...
static int GetNextInt32(RNGCryptoServiceProvider rnd)
{
byte[] randomInt = new byte[4];
rnd.GetBytes(randomInt);
return Convert.ToInt32(randomInt[0]);
}
OrderBy
在内部缓存排序键,否则还存在违反有序比较的传递属性的问题。如果曾经有调试模式验证OrderBy
产生了正确的结果,那么理论上它可能会引发异常。
以下实现使用Fisher-Yates算法(又称为Knuth Shuffle)。它运行时间为O(n),并在适当位置随机播放,因此尽管“代码随机排序”技术更多,但其性能要优于“随机排序”技术。有关一些比较性能的测量,请参见此处。我使用了System.Random,它适合用于非加密目的。*
static class RandomExtensions
{
public static void Shuffle<T> (this Random rng, T[] array)
{
int n = array.Length;
while (n > 1)
{
int k = rng.Next(n--);
T temp = array[n];
array[n] = array[k];
array[k] = temp;
}
}
}
用法:
var array = new int[] {1, 2, 3, 4};
var rng = new Random();
rng.Shuffle(array);
rng.Shuffle(array); // different order from first call to Shuffle
*对于更长的数组,为了使(极大)数量的排列具有同等可能性,有必要通过多次迭代来运行伪随机数生成器(PRNG),以使每次交换产生足够的熵。对于500个元素的数组,仅是可能的500个很小的部分!使用PRNG可以获得排列。尽管如此,Fisher-Yates算法没有偏见,因此混洗效果与您使用的RNG一样好。
array.Shuffle(new Random());
.. 会更好吗?
new Random()
使用基于当前系统时间的种子值初始化的,该种子值仅每〜16ms更新一次。
您正在寻找一种改组算法,对吗?
好的,有两种方法可以做到这一点:聪明但人们总是似乎误解了它并弄错了,所以也许它毕竟不是那个聪明的人方式和愚蠢的岩石,但谁在乎,因为它起作用了。
- 创建第一个数组的副本,但标记每个字符串应使用随机数。
- 根据随机数对重复数组进行排序。
该算法效果很好,但是请确保您的随机数生成器不太可能标记具有相同数字的两个字符串。由于所谓的“ 生日悖论”,因此发生这种情况的频率超出了您的预期。它的时间复杂度为O(n log n)。
我将其描述为递归算法:
随机播放大小为n的数组(索引在[0 .. n -1] 范围内):
如果n = 0如果n > 0
- 没做什么
- (递归步骤)随机播放数组的前n -1个元素
- 在[0 .. n -1] 范围内选择一个随机索引x
- 将索引n -1的元素与索引x的元素交换
迭代等效项是使迭代器遍历数组,并在进行过程中与随机元素交换,但是请注意,在迭代器指向的元素之后,您不能与元素交换。这是一个非常常见的错误,会导致随机播放。
时间复杂度为O(n)。
该算法简单但效率不高,O(N 2)。所有的“排序依据”算法通常为O(N log N)。在成千上万个元素以下,它可能没有什么区别,但对于大型列表而言,它不会起作用。
var stringlist = ... // add your values to stringlist
var r = new Random();
var res = new List<string>(stringlist.Count);
while (stringlist.Count >0)
{
var i = r.Next(stringlist.Count);
res.Add(stringlist[i]);
stringlist.RemoveAt(i);
}
它为O(N 2)的原因很微妙:List.RemoveAt()是O(N)操作,除非您从头开始依次删除。
您也可以使用Matt Howells进行扩展。例。
namespace System
{
public static class MSSystemExtenstions
{
private static Random rng = new Random();
public static void Shuffle<T>(this T[] array)
{
rng = new Random();
int n = array.Length;
while (n > 1)
{
int k = rng.Next(n);
n--;
T temp = array[n];
array[n] = array[k];
array[k] = temp;
}
}
}
}
然后,您可以像这样使用它:
string[] names = new string[] {
"Aaron Moline1",
"Aaron Moline2",
"Aaron Moline3",
"Aaron Moline4",
"Aaron Moline5",
"Aaron Moline6",
"Aaron Moline7",
"Aaron Moline8",
"Aaron Moline9",
};
names.Shuffle<string>();
随机化数组非常费力,因为您必须绕一堆字符串移动。为什么不只是从数组中随机读取?在最坏的情况下,您甚至可以使用getNextString()创建包装类。如果确实需要创建随机数组,则可以执行以下操作
for i = 0 -> i= array.length * 5
swap two strings in random places
* 5是任意的。
只是不去想,您可以这样做:
public string[] Randomize(string[] input)
{
List<string> inputList = input.ToList();
string[] output = new string[input.Length];
Random randomizer = new Random();
int i = 0;
while (inputList.Count > 0)
{
int index = r.Next(inputList.Count);
output[i++] = inputList[index];
inputList.RemoveAt(index);
}
return (output);
}
Jacco,您的解决方案使用自定义IComparer是不安全的。排序例程要求比较器符合多项要求才能正常运行。其中首先是一致性。如果在同一对对象上调用比较器,则它必须始终返回相同的结果。(比较也必须是可传递的)。
不能满足这些要求会在排序例程中引起许多问题,包括无限循环的可能性。
关于将随机数值与每个条目相关联然后按该值排序的解决方案,这会导致输出中的固有偏差,因为每当两个条目被分配相同的数值时,输出的随机性就会受到损害。(在“稳定”排序例程中,以输入中的第一者为准,以输出中的第一者为准。Array.Sort并非稳定,但基于Quicksort算法进行的分区仍然存在偏差。)
您需要考虑所需的随机性级别。如果您在一个扑克网站上需要加密的随机性级别,以保护自己免受确定的攻击者的侵害,则您的要求与只想将歌曲播放列表随机化的用户有很大不同。
对于歌曲列表混排,使用种子PRNG(例如System.Random)没有问题。对于一个扑克网站,它甚至不是一个选择,您需要比在栈溢出上为您做的任何事情都更难思考这个问题。(使用加密的RNG只是开始,您需要确保算法不会引入偏差,具有足够的熵源,并且不暴露任何会损害后续随机性的内部状态)。
这篇文章已经得到了很好的回答-使用Fisher-Yates随机播放的Durstenfeld实现,可获得快速而公正的结果。甚至已经发布了一些实现,尽管我注意到有些实际上是不正确的。
不久前,我写了几篇文章,介绍了使用这种技术来实现全部和部分混洗,(第二个链接是我希望增加价值的地方),以及关于如何检查实现是否公正的后续文章,可以用来检查任何随机播放算法。您可以在第二篇文章的末尾看到一个简单错误对随机数选择的影响。
好的,这显然是对我不利的一件事(对不起……),但是我经常使用一种相当通用且加密强度高的方法。
public static class EnumerableExtensions
{
static readonly RNGCryptoServiceProvider RngCryptoServiceProvider = new RNGCryptoServiceProvider();
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> enumerable)
{
var randomIntegerBuffer = new byte[4];
Func<int> rand = () =>
{
RngCryptoServiceProvider.GetBytes(randomIntegerBuffer);
return BitConverter.ToInt32(randomIntegerBuffer, 0);
};
return from item in enumerable
let rec = new {item, rnd = rand()}
orderby rec.rnd
select rec.item;
}
}
Shuffle()是任何IEnumerable的扩展,因此可以使用以下方法在列表中以随机顺序获取从0到1000的数字:
Enumerable.Range(0,1000).Shuffle().ToList()
该方法在排序时也不会让人感到惊讶,因为排序值是生成的,并且每个序列中的每个元素仅记住一次。
这是一个完整的工作控制台解决方案,基于此处提供的示例:
class Program
{
static string[] words1 = new string[] { "brown", "jumped", "the", "fox", "quick" };
static void Main()
{
var result = Shuffle(words1);
foreach (var i in result)
{
Console.Write(i + " ");
}
Console.ReadKey();
}
static string[] Shuffle(string[] wordArray) {
Random random = new Random();
for (int i = wordArray.Length - 1; i > 0; i--)
{
int swapIndex = random.Next(i + 1);
string temp = wordArray[i];
wordArray[i] = wordArray[swapIndex];
wordArray[swapIndex] = temp;
}
return wordArray;
}
}
int[] numbers = {0,1,2,3,4,5,6,7,8,9};
List<int> numList = new List<int>();
numList.AddRange(numbers);
Console.WriteLine("Original Order");
for (int i = 0; i < numList.Count; i++)
{
Console.Write(String.Format("{0} ",numList[i]));
}
Random random = new Random();
Console.WriteLine("\n\nRandom Order");
for (int i = 0; i < numList.Capacity; i++)
{
int randomIndex = random.Next(numList.Count);
Console.Write(String.Format("{0} ", numList[randomIndex]));
numList.RemoveAt(randomIndex);
}
Console.ReadLine();
这是使用OLINQ的一种简单方法:
// Input array
List<String> lst = new List<string>();
for (int i = 0; i < 500; i += 1) lst.Add(i.ToString());
// Output array
List<String> lstRandom = new List<string>();
// Randomize
Random rnd = new Random();
lstRandom.AddRange(from s in lst orderby rnd.Next(100) select s);
private ArrayList ShuffleArrayList(ArrayList source)
{
ArrayList sortedList = new ArrayList();
Random generator = new Random();
while (source.Count > 0)
{
int position = generator.Next(source.Count);
sortedList.Add(source[position]);
source.RemoveAt(position);
}
return sortedList;
}
sortedList = source.ToList().OrderBy(x => generator.Next()).ToArray();