使用.NET随机化数组的最佳方法

时间:2008-09-20 17:33:26

标签: c# .net algorithm sorting random

使用.NET随机化字符串数组的最佳方法是什么?我的数组包含大约500个字符串,我想用随机顺序创建一个具有相同字符串的新Array

请在答案中加入C#示例。

17 个答案:

答案 0 :(得分:181)

以下实现使用Fisher-Yates algorithm。它在O(n)时间内运行并随机播放,因此比“随机排序”技术表现更好,尽管它是更多的代码行。有关一些比较性能测量,请参阅here。我使用过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};
new Random().Shuffle(array);

*对于较长的数组,为了使(非常大的)排列数量同样可能,有必要通过多次迭代为每个交换运行伪随机数生成器(PRNG)以产生足够的熵。对于500个元素的阵列,只有500个元素的一小部分!使用PRNG可以获得排列。然而,Fisher-Yates算法是无偏的,因此随机播放将与您使用的RNG一样好。

答案 1 :(得分:149)

如果你使用的是.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)之类的东西,以免损坏您的数据,这将保护{{1也是。

此外,应该很好地理解System.Random作为熵源不是很强。如MSDN documentation中所述,如果您正在执行与安全相关的任何操作,则应使用从rnd派生的内容。例如:

System.Security.Cryptography.RandomNumberGenerator

...

using System.Security.Cryptography;

...

RNGCryptoServiceProvider rnd = new RNGCryptoServiceProvider();
string[] MyRandomArray = MyArray.OrderBy(x => GetNextInt32(rnd)).ToArray();

答案 2 :(得分:16)

你正在寻找一种改组算法,对吧?

好的,有两种方法可以做到这一点:聪明但人总是似乎误解了 - 它是错误的 - 所以也许 - 它不是那么聪明 - 总而言之,以及愚蠢而又愚蠢但又关心他们的工作方式。

愚蠢的方式

  
      
  • 创建第一个数组的副本,但标记每个字符串应该是一个随机数。
  •   
  • 根据随机数对重复数组进行排序。
  •   

此算法效果很好,但请确保您的随机数生成器不太可能使用相同的数字标记两个字符串。由于所谓的Birthday Paradox,这种情况比您预期的更频繁。它的时间复杂度为O( n log n )。

聪明的方式

我将其描述为递归算法:

  

改组大小为 n 的数组(范围为[0 .. n -1]的索引):

     如果 n = 0      
      
  • 什么都不做
  •   
     如果 n &gt; 0      
      
  • (递归步骤)随机播放数组的第一个 n -1个元素
  •   
  • 在[0 .. n -1]
  • 范围内选择随机索引 x   
  • 将索引 n -1处的元素与索引 x
  • 处的元素交换   

迭代等价物是通过数组遍历迭代器,随机交换随机元素,但请注意,在>迭代器指向的元素之后,您无法交换元素。这是一个非常常见的错误,导致有偏见的洗牌。

时间复杂度为O( n )。

答案 3 :(得分:8)

该算法简单但效率不高,O(N 2 )。所有“order by”算法通常为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)操作,除非你从最后按顺序删除。

答案 4 :(得分:4)

您也可以使用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>();

答案 5 :(得分:1)

随机化数组是非常密集的,因为你必须转移一堆字符串。为什么不随机从数组中读取?在最坏的情况下,您甚至可以使用getNextString()创建一个包装类。如果你真的需要创建一个随机数组,那么你可以做类似

的事情
for i = 0 -> i= array.length * 5
   swap two strings in random places

* 5是任意的。

答案 6 :(得分:1)

只要想到我的头脑,你就可以做到这一点:

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);
}

答案 7 :(得分:0)

Random r = new Random();
List<string> list = new List(originalArray);
List<string> randomStrings = new List();

while(list.Count > 0)
{
int i = r.Random(list.Count);
randomStrings.Add(list[i]);
list.RemoveAt(i);
}

答案 8 :(得分:0)

Jacco,您的解决方案是自定义IComparer是不安全的。 Sort例程要求比较器符合多个要求才能正常运行。首先是一致性。如果在同一对对象上调用比较器,则它必须始终返回相同的结果。 (比较也必须是可传递的)。

未能满足这些要求会导致排序例程中出现任何问题,包括无限循环的可能性。

关于将随机数值与每个条目相关联然后按该值排序的解决方案,这些导致输出中的固有偏差,因为任何时候两个条目被分配相同的数值,输出的随机性将妥协。 (在“稳定”排序例程中,输入中的第一个将是输出中的第一个.ArraySort不会稳定,但仍然存在基于Quicksort算法完成的分区的偏差)。

您需要考虑一下您需要的随机性级别。如果您正在运行一个扑克网站,您需要加密级别的随机性以防止确定的攻击者,那么您只需要想要随机化歌曲播放列表的人就会有非常不同的要求。

对于歌曲列表改组,使用种子PRNG(如System.Random)没有问题。对于一个扑克网站来说,它甚至都不是一个选项,你需要在堆栈溢出上比任何人为你做的事情想得更困难。 (使用加密RNG只是一个开始,您需要确保您的算法不会引入偏差,您有足够的熵源,并且您不会暴露任何会影响后续随机性的内部状态。)< / p>

答案 9 :(得分:0)

这篇文章已经得到了很好的回答 - 使用Durstenfeld对Fisher-Yates shuffle的实现来获得快速且无偏见的结果。甚至已经发布了一些实现,但我注意到一些实际上是不正确的。

我前后写了几篇关于implementing full and partial shuffles using this technique的帖子,(第二个链接是我希望增加价值的地方)a follow-up post about how to check whether your implementation is unbiased,可用于检查任何随机播放算法。您可以在第二篇文章的末尾看到随机数选择中的一个简单错误的影响。

答案 10 :(得分:0)

生成一组相同长度的随机浮点数或整数。对该数组进行排序,并在目标数组上进行相应的交换。

这产生了一种真正独立的排序。

答案 11 :(得分:0)

好的,这显然是我身边的一个颠簸(道歉...),但我经常使用一种非常通用且加密性强的方法。

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()

这种方法在排序方面也不会给出任何意外,因为排序值会在序列中的每个元素生成并记住一次。

答案 12 :(得分:0)

您不需要复杂的算法。

只需一个简单的界限:

Random random = new Random();
array.ToList().Sort((x, y) => random.Next(-1, 1)).ToArray();

请注意,如果您不首先使用Array,我们需要先将List转换为List

另外,请注意,这对于非常大的数组来说效率不高!否则它是干净的&amp;简单。

答案 13 :(得分:0)

此代码在数组中随机播放数字。

using System;

// ...
    static void Main(string[] args)
    {
        Console.ForegroundColor = ConsoleColor.Cyan;
        int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        Shuffle(numbers);

        for (int i = 0; i < numbers.Length; i++)
            Console.Write(numbers[i] + (i < numbers.Length - 1 ? ", " : null));
        Console.WriteLine();

        string[] words = { "this", "is", "a", "string", "of", "words" };
        Shuffle(words);

        for (int i = 0; i < words.Length; i++)
            Console.Write(words[i] + (i < words.Length - 1 ? ", " : null));
        Console.WriteLine();

        Console.ForegroundColor = ConsoleColor.Gray;
        Console.Write("Press any key to quit . . . ");
        Console.ReadKey(true);
    }

    static void Shuffle<T>(T[] array)
    {
        Random random = new Random();

        for (int i = 0; i < array.Length; i++)
        {
            T temporary = array[i];
            int intrandom = random.Next(array.Length);
            array[i] = array[intrandom];
            array[intrandom] = temporary;
        }
    }

答案 14 :(得分:0)

        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();

答案 15 :(得分:-1)

这是使用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);

答案 16 :(得分:-2)

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;
}