Tuesday 2 February 2010

When random is too consistent...

A while back I posted an extension method for shuffling Lists in C# using the Fisher Yates Shuffle. The algorithm is valid but my implementation relied on the default random number generator in .Net (System.Random) which is flawed since it generates a predictable sort of random.

Here's a correction that uses a more random sort of random from System.Security.Cryptography:
public static void Shuffle<T>(this IList<T> list)
{
var provider = new RNGCryptoServiceProvider();
int n = list.Count;
while (n > 1)
{
var box = new byte[1];
do provider.GetBytes(box);
while (!(box[0] < n * (Byte.MaxValue / n)));
var k = (box[0] % n);
n--;
var value = list[k];
list[k] = list[n];
list[n] = value;
}
}

Here's a simple test you can run to demonstrate the difference in the algorithms:

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

namespace AlphabetSoup
{
class Program
{
static void Main()
{
var alphabet = GetAlphabet();
foreach (var c in alphabet)
Console.Write(c);
Console.Write('\n');
Console.Write('\n');

Console.WriteLine("System.Random:");
for (int i = 0; i < 10; i++)
{
alphabet = GetAlphabet();
alphabet.QuickShuffle();
foreach (var c in alphabet)
Console.Write(c);
Console.Write('\n');
}
Console.Write('\n');
Console.Write('\n');

Console.WriteLine("System.Security.Cryptography:");
for (int i = 0; i < 10; i++)
{
alphabet = GetAlphabet();
alphabet.Shuffle();
foreach (var c in alphabet)
Console.Write(c);
Console.Write('\n');
}
Console.Write('\n');
Console.Write('\n');

Console.ReadKey();
}

static List<char> GetAlphabet()
{
var alphabet = new List<char>();
for (int c = 65; c < 91; c++)
alphabet.Add((char)c);
return alphabet;
}
}
static class Ext
{
public static void Shuffle<T>(this IList<T> list)
{
var provider = new RNGCryptoServiceProvider();
int n = list.Count;
while (n > 1)
{
var box = new byte[1];
do provider.GetBytes(box);
while (!(box[0] < n * (Byte.MaxValue / n)));
var k = (box[0] % n);
n--;
var value = list[k];
list[k] = list[n];
list[n] = value;
}
}
public static void QuickShuffle<T>(this IList<T> list)
{
var rng = new Random();
var n = list.Count;
while (n > 1)
{
n--;
var k = rng.Next(n + 1);
var value = list[k];
list[k] = list[n];
list[n] = value;
}
}
}
}

Here's the output on my machine: