Most efficient way to randomly “sort” (Shuffle) a list of integers in C#

前端 未结 12 1212
长发绾君心
长发绾君心 2020-11-22 12:25

I need to randomly \'sort\' a list of integers (0-1999) in the most efficient way possible. Any ideas?

Currently, I am doing something like this:

bo         


        
相关标签:
12条回答
  • 2020-11-22 12:43
    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;
    }
    

    modified to handle lists or other objects implementing IEnumerable

    0 讨论(0)
  • 2020-11-22 12:48

    As Greg pointed out the Fisher-Yates shuffle would be the best approach. Here is an implementation of the algorithm from Wikipedia:

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

    The implementation above relies on Random.nextInt(int) providing sufficiently random and unbiased results

    0 讨论(0)
  • 2020-11-22 12:48

    ICR's answer is very fast, but the resulting arrays aren't distributed normally. If you want a normal distribution, here's the code:

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

    Note that in my tests, this algorithm was 6 times slower than the one ICR provided, however this is the only way I could come up with to get a normal result distribution

    0 讨论(0)
  • 2020-11-22 12:51

    We can make an extension method out of this to get a Random enumerator for any IList collection

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

    This uses a reverse Fisher-Yates shuffle on the indexes of the list we want to randomly enumerate through. Its a bit of a size hog (allocating 4*list.Count bytes), but runs in O(n).

    0 讨论(0)
  • 2020-11-22 12:52

    I made a method using a temporary Hashtable, allowing the Hashtable's natural key sort to randomize. Simply add, read and discard.

    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
    
    0 讨论(0)
  • 2020-11-22 12:58

    A good linear-time shuffling algorithm is the Fisher-Yates shuffle.

    One problem you'll find with your proposed algorithm is that as you near the end of the shuffle, your loop will spend a lot of time looking for randomly chosen elements that have not yet been swapped. This may take an indeterminate amount of time once it gets to the last element to swap.

    Also, it looks like your algorithm will never terminate if there are an odd number of elements to sort.

    0 讨论(0)
提交回复
热议问题