Skip to main content
replaced http://codereview.stackexchange.com/ with https://codereview.stackexchange.com/
Source Link

After reading this questionthis question I wanted to write a short example of how a list or sequence could be shuffled in .net. The result turned out to not be very short:

After reading this question I wanted to write a short example of how a list or sequence could be shuffled in .net. The result turned out to not be very short:

After reading this question I wanted to write a short example of how a list or sequence could be shuffled in .net. The result turned out to not be very short:

Tweeted twitter.com/#!/StackCodeReview/status/616638788094283777
edited tags
Link
RubberDuck
  • 31.2k
  • 6
  • 74
  • 177
Source Link
Johnbot
  • 3.1k
  • 16
  • 19

Shuffling an arbitrary list or sequence

After reading this question I wanted to write a short example of how a list or sequence could be shuffled in .net. The result turned out to not be very short:

Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;

class Program
{
    private static void Main()
    {
        var sequence = Enumerable.Range(1, 10);
        Console.WriteLine(string.Join(", ", sequence.ProduceShuffle()));

        var list = sequence.ToList();
        Console.WriteLine(string.Join(", ", list));
        list.Shuffle();
        Console.WriteLine(string.Join(", ", list));

        using (var random = new RNGCryptoServiceProvider())
        {
            Console.WriteLine(string.Join(", ", sequence.ProduceShuffleStrong(random)));

            list.ShuffleStrong(random);
            Console.WriteLine(string.Join(", ", list));
        }
    }
}

ShuffleExtensions.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;

public static class ShuffleExtensions
{
    public static IEnumerable<T> ProduceShuffle<T>(this IEnumerable<T> source)
    {
        Check.NotNull(source, "source");

        return source.ProduceShuffle(new Random());
    }

    public static IEnumerable<T> ProduceShuffle<T>(this IEnumerable<T> source, Random random)
    {
        Check.NotNull(source, "source");
        Check.NotNull(random, "random");

        var values = source.ToList();

        values.Shuffle(random);

        return values;
    }

    public static void Shuffle<T>(this IList<T> source)
    {
        Check.NotNull(source, "source");

        source.Shuffle(new Random());
    }

    public static void Shuffle<T>(this IList<T> source, Random random)
    {
        Check.NotNull(source, "source");
        Check.NotNull(random, "random");

        var length = source.Count;

        for (var currentIndex = 0; currentIndex < length; currentIndex++)
        {
            var swapIndex = random.Next(currentIndex, length);
            source.Swap(currentIndex, swapIndex);
        }
    }

    public static IEnumerable<T> ProduceShuffleStrong<T>(this IEnumerable<T> source)
    {
        Check.NotNull(source, "source");

        using (var random = new RNGCryptoServiceProvider())
        {
            return source.ProduceShuffleStrong(random);
        }
    }

    public static IEnumerable<T> ProduceShuffleStrong<T>(this IEnumerable<T> source, RandomNumberGenerator random)
    {
        Check.NotNull(source, "source");
        Check.NotNull(random, "random");

        var values = source.ToList();

        values.ShuffleStrong(random);

        return values;
    }

    public static void ShuffleStrong<T>(this IList<T> source)
    {
        Check.NotNull(source, "source");

        using (var random = new RNGCryptoServiceProvider())
        {
            source.ShuffleStrong(random);
        }
    }

    public static void ShuffleStrong<T>(this IList<T> source, RandomNumberGenerator random)
    {
        Check.NotNull(source, "source");
        Check.NotNull(random, "random");

        var length = source.Count;

        for (var currentIndex = 0; currentIndex < length; currentIndex++)
        {
            var swapIndex = random.Next(currentIndex, length);
            source.Swap(currentIndex, swapIndex);
        }
    }

    internal static void Swap<T>(this IList<T> source, int firstIndex, int secondIndex)
    {
        DebugCheck.InRange(firstIndex, "firstIndex", 0, source.Count);
        DebugCheck.InRange(secondIndex, "secondIndex", 0, source.Count);

        var temp = source[firstIndex];
        source[firstIndex] = source[secondIndex];
        source[secondIndex] = temp;
    }
}

RandomNumberGeneratorExtensions.cs

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Cryptography;

public static class RandomNumberGeneratorExtensions
{
    public static int Next(this RandomNumberGenerator random, int maxValue)
    {
        Check.NotNull(random, "random");
        Check.GreaterThan(maxValue, "maxValue", 0);

        return random.Next(0, maxValue);
    }

    public static int Next(this RandomNumberGenerator random, int minValue, int maxValue)
    {
        Check.NotNull(random, "random");
        Check.GreaterThanOrEqual(minValue, "minValue", 0);
        Check.GreaterThan(maxValue, "maxValue", minValue, "minValue");

        var range = maxValue - minValue;
        var bytesForRange = GetBytesNeededForRange(range);
        var rangeMax = GetMaxForInt32ByteCount(bytesForRange);
        var biasThreshold = rangeMax - rangeMax % range;

        var rawRange = new byte[bytesForRange];

        while (true)
        {
            random.GetBytes(rawRange);
            var valueInByteRange = RawBytesToInt(rawRange, bytesForRange);
            if (valueInByteRange < biasThreshold)
            {
                var result = minValue + valueInByteRange % range;

                DebugCheck.InRange(result, "result", minValue, maxValue);

                return result;
            }
        }
    }

    private static int GetMaxForInt32ByteCount(int byteCount)
    {
        DebugCheck.InRangeInclusive(byteCount, "byteCount", 1, 4);

        switch (byteCount)
        {
            case 1:
                return 0x000000FF;
            case 2:
                return 0x0000FFFF;
            case 3:
                return 0x00FFFFFF;
            case 4:
                return 0x7FFFFFFF;
            default:
                throw new ArgumentOutOfRangeException(
                    "byteCount",
                    byteCount,
                    string.Format(
                        CultureInfo.InvariantCulture,
                        "byteCount was outside the range [1,4], actual {0}.",
                        byteCount)
                    );
        }
    }

    private static int GetBytesNeededForRange(int range)
    {
        DebugCheck.GreaterThanOrEqual(range, "range", 0);

        if (range > 0x00FFFFFF)
        {
            return 4;
        }
        if (range > 0x0000FFFF)
        {
            return 3;
        }
        if (range > 0x000000FF)
        {
            return 2;
        }
        return 1;
    }

    private static int RawBytesToInt(byte[] source, int byteCount)
    {
        DebugCheck.NotNull(source, "source");
        DebugCheck.InRangeInclusive(byteCount, "byteCount", 1, 4);

        int result;

        switch (byteCount)
        {
            case 1:
                result = source[0];
                break;
            case 2:
                result = source[0] |
                         source[1] << 8;
                break;
            case 3:
                result = source[0] |
                         source[1] << 8 |
                         source[2] << 16;
                break;
            case 4:
                result = source[0] |
                         source[1] << 8 |
                         source[2] << 16 |
                         source[3] << 24;

                result &= 0x7FFFFFFF;
                break;
            default:
                throw new ArgumentOutOfRangeException(
                    "byteCount",
                    byteCount,
                    string.Format(
                        CultureInfo.InvariantCulture,
                        "byteCount was outside the range [1,4], actual {0}.",
                        byteCount)
                    );
        }

        DebugCheck.GreaterThanOrEqual(result, "result", 0);

        return result;
    }
}

The shuffle used is the Fisher–Yates shuffle. The strong shuffles uses the RandomNumberGenerator to produce a greater number of the possible permutations for collections with more than 12 elements.

I have not included the source for Check and DebugCheck. Check throws exceptions when the condition isn't met and DebugCheck fails assertions. Methods in DebugCheck are also annotated with [Conditional("DEBUG")] so the checks are omitted in non-Debug builds.

Is there anything that isn't clear enough? Am I constructing the random range correctly using RandomNumberGenerator?