52

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:

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;
    }
}
Adam Jaskiewicz
  • 10,934
  • 3
  • 34
  • 37
Carl
  • 2,415
  • 3
  • 17
  • 7
  • Note that you create an iTemp var, but do not use it. This will cause issues of course. – Aistina Dec 17 '08 at 17:49
  • ahh, yeah. I meant to assign values[iCurIndex] = iTemp. – Carl Dec 17 '08 at 17:54
  • 2
    A better way of saying this would probably be "Most efficient way to create a random permutation of a list of integers" – ICR Dec 17 '08 at 18:06
  • Possible duplicate of [Best way to randomize a string array with .NET](http://stackoverflow.com/questions/108819/best-way-to-randomize-a-string-array-with-net) – Ruben Bartelink Oct 24 '15 at 16:17

13 Answers13

55

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.

Greg Hewgill
  • 951,095
  • 183
  • 1,149
  • 1,285
  • 1
    Unless the algorithm has been edited since your answer, there will be no slow down near the end of the shuffle. iCurIndex is never assigned to other then in the for statement. What will happen however is that there will likely be a number of unsorted elements whenever iCurIndex == iSwapIndex. – Ash Nov 07 '09 at 16:29
  • It's a nitpick, but the Fisher-Yates algorithm can't actually achieve linear complexity, nor can any shuffle, because to randomly pick among `n!` permutations you must generate at least `log(n!)` bits of entropy. – Owen Mar 10 '21 at 05:49
32
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

ICR
  • 13,896
  • 4
  • 50
  • 78
  • How would the above be called if I had an arraylist with just strings in it? – Matthew Lock Aug 05 '10 at 01:58
  • 6
    `random.Next(i+1, array.Length)` to avoid `if` checking. Also `i < array.Lenth-1`, because we won't swap the same (last) element. – kolobok Jan 12 '12 at 15:56
  • 2
    Old thread - but just in case anyone is thinking about copying the above code - it does not work correctly. The first element in the list is never selected - Ever! – dotnetnoob May 18 '13 at 09:49
  • 1
    @akapelko By using `random.Next(i+1, array.Length)` you eliminate the possibility of it swapping with itself, which is necessary to provide an even distribution of possibilities. The if statement is actually just a shortcut to avoid doing the work of swapping with itself. – ICR May 20 '13 at 11:39
  • @dotnetnoob Cheers. If I understand your point correctly, I think I've fixed the code. Can you confirm? – ICR May 20 '13 at 11:39
  • 2
    This is also implemented in MoreLinq (not yet released in its NuGet though): http://code.google.com/p/morelinq/source/browse/MoreLinq/RandomSubset.cs – angularsen Dec 11 '13 at 09:37
  • More linq is great – rollsch Mar 19 '19 at 03:08
18

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).

foson
  • 10,037
  • 2
  • 35
  • 53
7

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

Community
  • 1
  • 1
Micah
  • 111,873
  • 86
  • 233
  • 325
4

I am not sure of the efficiency factor, but I have used something similar to the following, if you aren't opposed to using an 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;
}

Using this, you do not have to worry about the intermediate swapping.

Joseph Ferris
  • 12,576
  • 3
  • 46
  • 72
  • 2
    Array.RemoveAt is an O(n) operation, and each iteration of your loop decreases the size of the source array by 1. This makes your functions complexity is equivalent to the Summation of n from array.count to 0, or O((n^2+n)/2). It works, but its not very efficient. – Juliet Dec 17 '08 at 18:10
2
itemList.OrderBy(x=>Guid.NewGuid()).Take(amount).ToList()
Dmitry
  • 661
  • 5
  • 21
2

To improve your efficiency you can keep a set of values/indices that have been swapped rather than a boolean for indicating they were swapped. Pick your randomized swap index from the remaining pool. When the pool is 0, or when you made it through the initial list then you are done. You don't have the potential to try to select a random swap index value.

When you do a swap, just remove them from the pool.

For the size of data you are looking at it is no big deal.

Tim
  • 20,184
  • 24
  • 117
  • 214
1

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

Arsen Zahray
  • 24,367
  • 48
  • 131
  • 224
0

what about :

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

voila!

que dal
  • 1
  • 1
0

Wouldn't something like this work?

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));
  • Yes, but it wouldn't be efficient for large lists -- sorting is O(n log n), where Fisher Yates is Linear. – Thelema Dec 17 '08 at 18:04
  • int[] or IEnumerable doesn't have a sort method, only List – foson Dec 17 '08 at 19:16
  • 1
    I know, this is an ancient answer, but this question still comes up in a Google search: NEVER DO THIS. It will *not* shuffle your list randomly. Your list will be significantly more likely to be in certain orders than in others. – Daniel Martin Apr 11 '11 at 13:30
  • This might end up in an endless loop, since the typical `Comparator` interfaces are required to be stable, antisymmetrical and transitive. What if the implementation of `list.Sort` uses a bubble sort? – Roland Illig Jul 09 '11 at 21:40
  • liable to "Unable to sort because the IComparer.Compare() method returns inconsistent results. Either a value does not compare equal to itself, or one value repeatedly compared to another value yields different results. x: '', x's type: 'String', IComparer: ''." – Arsen Zahray Mar 07 '12 at 13:07
0

Here is what I used. This is surely not the fastest one, but it is probably good enough for most cases and most importantly, it is very simple.

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

You can use a NuGet package called ListShuffle (source code) to shuffle a list in a thread-safe way using the Fisher-Yates algorithm, with optional cryptographically-strong random.

var myList = new List<string>();
myList.Add("Item A");
myList.Add("Item B");
myList.Add("Item C");

myList.Shuffle();

or (less performant but cryptographically-strong)

var myList = new List<string>();
myList.Add("Item A");
myList.Add("Item B");
myList.Add("Item C");

myList.CryptoStrongShuffle();
Mark Cilia Vincenti
  • 1,410
  • 8
  • 25
-1

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
Jim Conte
  • 7
  • 1