Is using Random and OrderBy a good shuffle algorithm?

前端 未结 12 1547
-上瘾入骨i
-上瘾入骨i 2020-11-21 11:41

I have read an article about various shuffle algorithms over at Coding Horror. I have seen that somewhere people have done this to shuffle a list:

var r = ne         


        
相关标签:
12条回答
  • 2020-11-21 12:05

    It's not a way of shuffling that I like, mostly on the grounds that it's O(n log n) for no good reason when it's easy to implement an O(n) shuffle. The code in the question "works" by basically giving a random (hopefully unique!) number to each element, then ordering the elements according to that number.

    I prefer Durstenfield's variant of the Fisher-Yates shuffle which swaps elements.

    Implementing a simple Shuffle extension method would basically consist of calling ToList or ToArray on the input then using an existing implementation of Fisher-Yates. (Pass in the Random as a parameter to make life generally nicer.) There are plenty of implementations around... I've probably got one in an answer somewhere.

    The nice thing about such an extension method is that it would then be very clear to the reader what you're actually trying to do.

    EDIT: Here's a simple implementation (no error checking!):

    public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random rng)
    {
        T[] elements = source.ToArray();
        // Note i > 0 to avoid final pointless iteration
        for (int i = elements.Length-1; i > 0; i--)
        {
            // Swap element "i" with a random earlier element it (or itself)
            int swapIndex = rng.Next(i + 1);
            T tmp = elements[i];
            elements[i] = elements[swapIndex];
            elements[swapIndex] = tmp;
        }
        // Lazily yield (avoiding aliasing issues etc)
        foreach (T element in elements)
        {
            yield return element;
        }
    }
    

    EDIT: Comments on performance below reminded me that we can actually return the elements as we shuffle them:

    public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random rng)
    {
        T[] elements = source.ToArray();
        for (int i = elements.Length - 1; i >= 0; i--)
        {
            // Swap element "i" with a random earlier element it (or itself)
            // ... except we don't really need to swap it fully, as we can
            // return it immediately, and afterwards it's irrelevant.
            int swapIndex = rng.Next(i + 1);
            yield return elements[swapIndex];
            elements[swapIndex] = elements[i];
        }
    }
    

    This will now only do as much work as it needs to.

    Note that in both cases, you need to be careful about the instance of Random you use as:

    • Creating two instances of Random at roughly the same time will yield the same sequence of random numbers (when used in the same way)
    • Random isn't thread-safe.

    I have an article on Random which goes into more detail on these issues and provides solutions.

    0 讨论(0)
  • 2020-11-21 12:07

    Looking for an algorithm? You can use my ShuffleList class:

    class ShuffleList<T> : List<T>
    {
        public void Shuffle()
        {
            Random random = new Random();
            for (int count = Count; count > 0; count--)
            {
                int i = random.Next(count);
                Add(this[i]);
                RemoveAt(i);
            }
        }
    }
    

    Then, use it like this:

    ShuffleList<int> list = new ShuffleList<int>();
    // Add elements to your list.
    list.Shuffle();
    

    How does it work?

    Let's take an initial sorted list of the 5 first integers: { 0, 1, 2, 3, 4 }.

    The method starts by counting the nubmer of elements and calls it count. Then, with count decreasing on each step, it takes a random number between 0 and count and moves it to the end of the list.

    In the following step-by-step example, the items that could be moved are italic, the selected item is bold:

    0 1 2 3 4
    0 1 2 3 4
    0 1 2 4 3
    0 1 2 4 3
    1 2 4 3 0
    1 2 4 3 0
    1 2 3 0 4
    1 2 3 0 4
    2 3 0 4 1
    2 3 0 4 1
    3 0 4 1 2

    0 讨论(0)
  • 2020-11-21 12:12

    This has come up many times before. Search for Fisher-Yates on StackOverflow.

    Here is a C# code sample I wrote for this algorithm. You can parameterize it on some other type, if you prefer.

    static public class FisherYates
    {
            //      Based on Java code from wikipedia:
            //      http://en.wikipedia.org/wiki/Fisher-Yates_shuffle
            static public void Shuffle(int[] deck)
            {
                    Random r = new Random();
                    for (int n = deck.Length - 1; n > 0; --n)
                    {
                            int k = r.Next(n+1);
                            int temp = deck[n];
                            deck[n] = deck[k];
                            deck[k] = temp;
                    }
            }
    }
    
    0 讨论(0)
  • 2020-11-21 12:12

    I found Jon Skeet's answer to be entirely satisfactory, but my client's robo-scanner will report any instance of Random as a security flaw. So I swapped it out for System.Security.Cryptography.RNGCryptoServiceProvider. As a bonus, it fixes that thread-safety issue that was mentioned. On the other hand, RNGCryptoServiceProvider has been measured as 300x slower than using Random.

    Usage:

    using (var rng = new RNGCryptoServiceProvider())
    {
        var data = new byte[4];
        yourCollection = yourCollection.Shuffle(rng, data);
    }
    

    Method:

    /// <summary>
    /// Shuffles the elements of a sequence randomly.
    /// </summary>
    /// <param name="source">A sequence of values to shuffle.</param>
    /// <param name="rng">An instance of a random number generator.</param>
    /// <param name="data">A placeholder to generate random bytes into.</param>
    /// <returns>A sequence whose elements are shuffled randomly.</returns>
    public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, RNGCryptoServiceProvider rng, byte[] data)
    {
        var elements = source.ToArray();
        for (int i = elements.Length - 1; i >= 0; i--)
        {
            rng.GetBytes(data);
            var swapIndex = BitConverter.ToUInt32(data, 0) % (i + 1);
            yield return elements[swapIndex];
            elements[swapIndex] = elements[i];
        }
    }
    
    0 讨论(0)
  • 2020-11-21 12:14

    Slightly unrelated, but here is an interesting method (that even though it is really excessibe, has REALLY been implemented) for truly random generation of dice rolls!

    Dice-O-Matic

    The reason I'm posting this here, is that he makes some interesting points about how his users reacted to the idea of using algorithms to shuffle, over actual dice. Of course, in the real world, such a solution is only for the really extreme ends of the spectrum where randomness has such an big impact and perhaps the impact affects money ;).

    0 讨论(0)
  • 2020-11-21 12:16

    This algorithm shuffles by generating a new random value for each value in a list, then ordering the list by those random values. Think of it as adding a new column to an in-memory table, then filling it with GUIDs, then sorting by that column. Looks like an efficient way to me (especially with the lambda sugar!)

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