【问题标题】:Most efficient way to randomly "sort" (Shuffle) a list of integers in C#在 C# 中随机“排序”(随机播放)整数列表的最有效方法
【发布时间】:2010-09-27 09:11:38
【问题描述】:

我需要以最有效的方式随机“排序”整数列表 (0-1999)。有什么想法吗?

目前,我正在做这样的事情:

bool[] bIndexSet = new bool[iItemCount];

for (int iCurIndex = 0; iCurIndex < iItemCount; iCurIndex++)
{
    int iSwapIndex = random.Next(iItemCount);
    if (!bIndexSet[iSwapIndex] && iSwapIndex != iCurIndex)
    {
        int iTemp = values[iSwapIndex];
        values[iSwapIndex] = values[iCurIndex];
        values[iCurIndex] = values[iSwapIndex];
        bIndexSet[iCurIndex] = true;
        bIndexSet[iSwapIndex] = true;
    }
}

【问题讨论】:

  • 请注意,您创建了一个 iTemp 变量,但不要使用它。这当然会引起问题。
  • 啊,是的。我的意思是分配值[iCurIndex] = iTemp.
  • 更好的说法可能是“创建整数列表的随机排列的最有效方法”

标签: c# random shuffle


【解决方案1】:

一个好的线性时间洗牌算法是Fisher-Yates shuffle

您会发现您提出的算法的一个问题是,当您接近 shuffle 结束时,您的循环将花费大量时间来寻找尚未交换的随机选择的元素。一旦到达最后一个要交换的元素,这可能需要不确定的时间。

此外,如果要排序的元素数量为奇数,您的算法似乎永远不会终止。

【讨论】:

  • 除非在您回答后算法已被编辑,否则在随机播放结束时不会减速。 iCurIndex 永远不会在 for 语句中分配给 other then。然而,当 iCurIndex == iSwapIndex 时,可能会有许多未排序的元素。
  • 这是一个挑剔的问题,但 Fisher-Yates 算法实际上无法实现线性复杂度,也无法进行任何洗牌,因为要在 n! 排列中随机挑选,您必须至少生成 log(n!) 位熵。
【解决方案2】:

为了提高您的效率,您可以保留一组已交换的值/索引,而不是用于指示它们已交换的布尔值。从剩余的池中选择您的随机交换索引。当池为 0 时,或者当您通过初始列表时,您就完成了。您没有可能尝试选择随机掉期指数值。

当您进行交换时,只需将它们从池中移除即可。

对于您正在查看的数据大小而言,这没什么大不了的。

【讨论】:

    【解决方案3】:

    我不确定效率因素,但如果您不反对使用 ArrayList,我使用了类似于以下内容的内容:

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

    使用它,您不必担心中间交换。

    【讨论】:

    • Array.RemoveAt 是一个 O(n) 操作,并且循环的每次迭代都会将源数组的大小减小 1。这使您的函数复杂度相当于数组中 n 的总和。计数到 0,或 O((n^2+n)/2)。它有效,但效率不高。
    【解决方案4】:

    正如 Greg 指出的那样,Fisher-Yates shuffle 将是最好的方法。这是来自维基百科的算法的实现:

    public static void shuffle (int[] array)
    {
       Random rng = new Random();   // i.e., java.util.Random.
       int n = array.length;        // The number of items left to shuffle (loop invariant).
       while (n > 1)
       {
          int k = rng.nextInt(n);  // 0 <= k < n.
          n--;                     // n is now the last pertinent index;
          int temp = array[n];     // swap array[n] with array[k] (does nothing if k == n).
          array[n] = array[k];
          array[k] = temp;
       }
    }
    

    上面的实现依赖于 Random.nextInt(int) 提供 足够随机和无偏 结果

    【讨论】:

    • 我在 VB.NET 中使用了这个解决方案,并且工作起来非常棒! :) 谢谢
    • @MathieuG 8 年后,micah 的努力有了收获! ;)
    【解决方案5】:

    这样的东西不行吗?

    var list = new[]{0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
    var random = new Random();
    list.Sort((a,b)=>random.Next(-1,1));
    

    【讨论】:

    • 是的,但是对于大型列表来说效率不高——排序是 O(n log n),其中 Fisher Yates 是线性的。
    • int[] 或 IEnumerable 没有排序方法,只有 List
    • 我知道,这是一个古老的答案,但这个问题仍然出现在谷歌搜索中:永远不要这样做。它不会随机打乱您的列表。与其他人相比,您的列表更有可能按某些顺序排列。
    • 这可能会陷入死循环,因为典型的Comparator 接口需要稳定、反对称和传递。如果list.Sort 的实现使用冒泡排序呢?
    • liable to "无法排序,因为 IComparer.Compare() 方法返回不一致的结果。一个值不与自身比较,或者一个值与另一个值重复比较会产生不同的结果。x: '',x 的类型:'String',IComparer:''。"
    【解决方案6】:
    static Random random = new Random();
    
    public static IEnumerable<T> RandomPermutation<T>(IEnumerable<T> sequence)
    {
        T[] retArray = sequence.ToArray();
    
    
        for (int i = 0; i < retArray.Length - 1; i += 1)
        {
            int swapIndex = random.Next(i, retArray.Length);
            if (swapIndex != i) {
                T temp = retArray[i];
                retArray[i] = retArray[swapIndex];
                retArray[swapIndex] = temp;
            }
        }
    
        return retArray;
    }
    

    修改为处理列表或其他实现 IEnumerable 的对象

    【讨论】:

    • 如果我有一个只有字符串的数组列表,如何调用上述内容?
    • random.Next(i+1, array.Length) 避免if 检查。还有i &lt; array.Lenth-1,因为我们不会交换相同的(最后一个)元素。
    • 旧线程 - 但以防万一有人考虑复制上述代码 - 它无法正常工作。列表中的第一个元素永远不会被选中 - 永远!
    • @akapelko 通过使用random.Next(i+1, array.Length),您消除了它与自身交换的可能性,这是提供均匀分布可能性所必需的。 if 语句实际上只是避免与自身进行交换工作的捷径。
    • 这也在 MoreLinq 中实现(虽然尚未在其 NuGet 中发布):code.google.com/p/morelinq/source/browse/MoreLinq/…
    【解决方案7】:

    我们可以用这个扩展方法来获取任何 IList 集合的随机枚举器

    class Program
    {
        static void Main(string[] args)
        {
            IList<int> l = new List<int>();
            l.Add(7);
            l.Add(11);
            l.Add(13);
            l.Add(17);
    
            foreach (var i in l.AsRandom())
                Console.WriteLine(i);
    
            Console.ReadLine();
        }
    }
    
    
    public static class MyExtensions
    {
        public static IEnumerable<T> AsRandom<T>(this IList<T> list)
        {
            int[] indexes = Enumerable.Range(0, list.Count).ToArray();
            Random generator = new Random();
    
            for (int i = 0; i < list.Count; ++i )
            {
                int position = generator.Next(i, list.Count);
    
                yield return list[indexes[position]];
    
                indexes[position] = indexes[i];
            }
        }
    }   
    

    这对我们要随机枚举的列表的索引使用反向 Fisher-Yates 洗牌。它的大小有点大(分配 4*list.Count 字节),但在 O(n) 中运行。

    【讨论】:

      【解决方案8】:

      我使用临时 Hashtable 制作了一个方法,允许 Hashtable 的自然键排序随机化。只需添加、读取和丢弃。

      int min = 1;
      int max = 100;
      Random random;
      Hashtable hash = new Hashtable();
      for (int x = min; x <= max; x++)
      {
          random = new Random(DateTime.Now.Millisecond + x);
          hash.Add(random.Next(Int32.MinValue, Int32.MaxValue), x);
      }
      foreach (int key in hash.Keys)
      {
          HttpContext.Current.Response.Write("<br/>" + hash[key] + "::" + key);
      }
      hash.Clear(); // cleanup
      

      【讨论】:

      • GetHashCode() 绝不保证任何随机化。
      【解决方案9】:
      itemList.OrderBy(x=>Guid.NewGuid()).Take(amount).ToList()
      

      【讨论】:

        【解决方案10】:

        ICR 的回答非常快,但生成的数组分布不正常。如果你想要一个正态分布,下面是代码:

            public static IEnumerable<T> RandomPermutation<T>(this IEnumerable<T> sequence, int start,int end)
            {
                T[] array = sequence as T[] ?? sequence.ToArray();
        
                var result = new T[array.Length];
        
                for (int i = 0; i < start; i++)
                {
                    result[i] = array[i];
                }
                for (int i = end; i < array.Length; i++)
                {
                    result[i] = array[i];
                }
        
                var sortArray=new List<KeyValuePair<double,T>>(array.Length-start-(array.Length-end));
                lock (random)
                {
                    for (int i = start; i < end; i++)
                    {
                        sortArray.Add(new KeyValuePair<double, T>(random.NextDouble(), array[i]));
                    }
                }
        
                sortArray.Sort((i,j)=>i.Key.CompareTo(j.Key));
        
                for (int i = start; i < end; i++)
                {
                    result[i] = sortArray[i - start].Value;
                }
        
                return result;
            }
        

        请注意,在我的测试中,该算法比提供的 ICR 慢 6 倍,但这是我能想出的获得正态结果分布的唯一方法

        【讨论】:

          【解决方案11】:

          怎么样:

          System.Array.Sort(arrayinstance, RandomizerMethod);
          ...
          //any evoluated random class could do it !
          private static readonly System.Random Randomizer = new System.Random();
          
          private static int RandomizerMethod<T>(T x, T y)
              where T : IComparable<T>
          {
              if (x.CompareTo(y) == 0)
                  return 0;
          
              return Randomizer.Next().CompareTo(Randomizer.Next());
          }
          

          瞧!

          【讨论】:

            【解决方案12】:

            这是我使用的。 这肯定不是最快的,但对于大多数情况来说它可能已经足够好了,最重要的是,它非常简单。

            IEnumerable<ListItem> list = ...;
            Random random = new Random(); // important to not initialize a new random in the OrderBy() function
            return list.OrderBy(i => random.Next());
            

            【讨论】:

              猜你喜欢
              • 2011-05-02
              • 1970-01-01
              • 2023-01-23
              • 1970-01-01
              • 2010-12-15
              • 2014-08-29
              • 2014-04-02
              • 2010-12-21
              • 2012-03-11
              相关资源
              最近更新 更多