web-dev-qa-db-fra.com

Le moyen le plus efficace de "trier" de manière aléatoire une liste d'entiers en C #

J'ai besoin de 'trier' de manière aléatoire une liste d'entiers (0-1999) de la manière la plus efficace possible. Des idées?

Actuellement, je fais quelque chose comme ça:

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

Un bon algorithme de brassage à temps linéaire est le shuffle de Fisher-Yates .

L’un des problèmes que vous rencontrerez dans l’algorithme que vous proposez est qu’à la fin du shuffle, votre boucle passera beaucoup de temps à rechercher des éléments choisis au hasard qui n’ont pas encore été permutés. Cela peut prendre un temps indéterminé une fois le dernier élément à échanger.

De plus, il semble que votre algorithme ne se termine jamais s'il y a un nombre impair d'éléments à trier.

53
Greg Hewgill
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;
}

modifié pour gérer des listes ou d'autres objets implémentant IEnumerable

30
ICR

Nous pouvons en faire une méthode d'extension pour obtenir un énumérateur aléatoire pour toute collection IList

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

Cette opération utilise une lecture inversée de Fisher-Yates sur les index de la liste que nous souhaitons énumérer de manière aléatoire. Son un peu d'une taille de porc (l'allocation de 4 * list.Count bytes), mais s'exécute dans O (n). 

18
foson

Comme Greg l'a souligné, le meilleur { Fisher-Yates shuffle } serait la meilleure approche. Voici une implémentation de l'algorithme de 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;
   }
}

La mise en œuvre ci-dessus repose sur Random.nextInt (int) fournissant suffisamment aléatoire et non biaisé résultats

5
Micah

Je ne suis pas sûr du facteur d’efficacité, mais j’ai utilisé quelque chose de similaire au suivant si vous n’êtes pas opposé à l’utilisation de 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;
}

Avec cela, vous n'avez pas à vous soucier de la permutation intermédiaire.

4
Joseph Ferris

Pour améliorer votre efficacité, vous pouvez conserver un ensemble de valeurs/indices qui ont été permutés plutôt qu'un booléen indiquant leur remplacement. Choisissez votre index d'échange aléatoire dans le pool restant. Lorsque le pool a la valeur 0 ou lorsque vous avez parcouru la liste initiale, vous avez terminé. Vous n'avez pas le potentiel d'essayer de sélectionner une valeur d'index de permutation aléatoire. 

Lorsque vous effectuez un échange, retirez-les simplement de la piscine.

Pour la taille des données que vous consultez, ce n'est pas grave.

2
Tim

La réponse d'ICR est très rapide, mais les tableaux résultants ne sont pas distribués normalement. Si vous voulez une distribution normale, voici le 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;
    }

Notez que dans mes tests, cet algorithme était 6 fois plus lent que celui fourni par ICR, mais c’est la seule façon pour moi d’obtenir une distribution de résultat normale

1
Arsen Zahray
itemList.OrderBy(x=>Guid.NewGuid()).Take(amount).ToList()
1
Dmitry

qu'en est-il de :

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

le tour est joué!

0
que dal

Quelque chose ne va pas comme ça?

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));
0
Dan

Je suppose que les deux dernières lignes doivent être interchangées dans la réponse de Micah. Donc, le code pourrait ressembler à

 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.Next(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;

        }
    }
0
nKnight