web-dev-qa-db-fra.com

Entrée aléatoire du dictionnaire

Quel est le meilleur moyen d’obtenir une entrée aléatoire d’un dictionnaire en c #?

Le dictionnaire doit contenir plusieurs objets aléatoires à afficher sur une page. Toutefois, je ne peux pas utiliser les éléments suivants, car les dictionnaires ne sont pas accessibles par index:

Random Rand = new Random();
Dictionary< string, object> dict = GetDictionary();
return dict[Rand.Next()];

Aucune suggestion?

48
Ed James

Mise à jour pour utiliser les génériques, être encore plus rapide et expliquer pourquoi cette option est plus rapide.

Cette réponse est similaire aux autres réponses, mais puisque vous avez dit que vous avez besoin de "plusieurs éléments aléatoires", cela sera plus performant:

public IEnumerable<TValue> RandomValues<TKey, TValue>(IDictionary<TKey, TValue> dict)
{
    Random Rand = new Random();
    List<TValue> values = Enumerable.ToList(dict.Values);
    int size = dict.Count;
    while(true)
    {
        yield return values[Rand.Next(size)];
    }
}

Vous pouvez utiliser cette méthode comme suit:

Dictionary<string, object> dict = GetDictionary();
foreach (object value in RandomValues(dict).Take(10))
{
    Console.WriteLine(value);
}

Cela a amélioré les performances par rapport aux autres réponses (y compris la réponse de yshuditelu).

  1. Il n'est pas nécessaire de créer une nouvelle collection de tous les éléments du dictionnaire chaque fois que vous souhaitez extraire une nouvelle valeur aléatoire. C’est vraiment grave si votre dictionnaire contient beaucoup d’éléments.
  2. Il n'est pas nécessaire d'effectuer une recherche basée sur la clé du dictionnaire chaque fois que vous extrayez une valeur aléatoire. Pas aussi important que le n ° 1, mais c'est encore deux fois plus vite que ça.

Mes tests montrent qu'avec 1000 objets dans le dictionnaire, cette méthode est environ 70 fois plus rapide que les autres méthodes suggérées.

41
StriplingWarrior

Si vous utilisez .net 3.5, Enumerable a une méthode d’extension ElementAt qui vous permettrait de faire:

return dict.ElementAt(Rand.Next(0, dict.Count)).Value;
45
Timothy Carter

De votre dictionnaire ...

Dictionary<string, int> dict = new Dictionary<string, object>()

vous pouvez créer une liste complète de clés ...

List<string> keyList = new List<string>(dict.Keys);

et ensuite sélectionnez une clé aléatoire dans votre liste.

Random Rand = new Random();
string randomKey = keyList[Rand.Next(keyList.Count)];

Puis simplement retournez l'objet aléatoire correspondant à cette clé.

return dict[randomKey];
16
Robert Cartaino

Mon autre réponse est correcte pour la question et serait utile dans de nombreux cas, comme obtenir des informations de jet de dés personnalisés (le jet de chaque dé est aléatoire, indépendant des autres dés). Cependant, vos commentaires donnent à penser que vous espériez peut-être obtenir une série d’éléments "uniques" dans la variable Dictionary, un peu comme si vous distribuiez des cartes d’un jeu. Une fois qu'une carte est distribuée, vous ne voudrez plus jamais revoir la même carte tant que vous ne la mélangez plus. Dans ce cas, la meilleure stratégie dépendra de ce que vous faites. 

Si vous ne récupérez que quelques éléments d'une Dictionary large, vous devriez pouvoir adapter mon autre réponse en supprimant l'élément aléatoire de la liste à chaque fois qu'une nouvelle est récupérée. Vous voudrez probablement aussi faire de la liste une LinkedList, car même s'il sera plus lent de trouver un élément par son index, il est beaucoup moins coûteux de supprimer des éléments au milieu de celui-ci. Le code pour cela serait un peu plus compliqué, donc si vous êtes prêt à sacrifier certaines performances pour plus de simplicité, vous pouvez simplement le faire:

public IEnumerable<TValue> UniqueRandomValues<TKey, TValue>(IDictionary<TKey, TValue> dict)
{
    Random Rand = new Random();
    Dictionary<TKey, TValue> values = new Dictionary<TKey, TValue>(dict);
    while(values.Count > 0)
    {
        TKey randomKey = values.Keys.ElementAt(Rand.Next(0, values.Count));  // hat tip @yshuditelu 
        TValue randomValue = values[randomKey];
        values.Remove(randomKey);
        yield return randomValue;
    }
}

Si, en revanche, vous prévoyez d'extraire un nombre important d'éléments de votre dictionnaire (c.-à-d. En distribuer plus que de vous connecter (n) à votre "deck"), vous ferez mieux de mélanger tout d'abord votre deck entier. , puis en tirant par le haut:

public IEnumerable<TValue> UniqueRandomValues<TKey, TValue>(IDictionary<TKey, TValue> dict)
{
    // Put the values in random order
    Random Rand = new Random();
    LinkedList<TValue> values = new LinkedList<TValue>(from v in dict.Values
                                                       orderby Rand.Next()
                                                       select v);
    // Remove the values one at a time
    while(values.Count > 0)
    {
        yield return values.Last.Value;
        values.RemoveLast();
    }
}

Le crédit va à ookii.org pour le code de mélange simple. Si ce n'est toujours pas ce que vous cherchiez, vous pourriez peut-être poser une nouvelle question avec plus de détails sur ce que vous essayez de faire.

12
StriplingWarrior

Quelque chose comme:

Random Rand = new Random();
Dictionary dict = GetDictionary();
var k = dict.Keys.ToList()[Rand.Next(dict.Count)];
return dict[k];
3
Henk Holterman

Ce ne sera pas très rapide, mais cela devrait marcher:

Random Rand = new Random();
Dictionary dict = GetDictionary();
return dict.Skip(Rand.Next(dict.Count)).First().Value;
2
Jonathan Rupp

Une solution simple consisterait à utiliser la méthode d'extension ToList() et à utiliser l'index de la liste.

Si vous avez simplement besoin des valeurs ou des clés (pas la paire clé/valeur), renvoyez ces collections du dictionnaire et utilisez également ToList() .

        Random Rand = new Random();
        Dictionary<string, object> dict = GetDictionary();
        var k = dict.ToList()[Rand.Next(dict.Count)];
        // var k = dict.Values.ToList()[Rand.Next(dict.Count)];
        // var k = dict.Keys.ToList()[Rand.Next(dict.Count)];

        Console.WriteLine("Random dict pair {0} = {1}", k.Key, k.Value);
1
bruno conde

Je crois que le seul moyen est de créer une liste séparée de KeyValuePairs en premier.

0
Groo
public static class DictionaryExtensions
{
    public static TKey[] Shuffle<TKey, TValue>(
       this System.Collections.Generic.Dictionary<TKey, TValue> source)
    {
        Random r = new Random();
        TKey[] wviTKey = new TKey[source.Count];
        source.Keys.CopyTo(wviTKey, 0);

        for (int i = wviTKey.Length; i > 1; i--)
        {
            int k = r.Next(i);
            TKey temp = wviTKey[k];
            wviTKey[k] = wviTKey[i - 1];
            wviTKey[i - 1] = temp;
        }

        return wviTKey;
    }
}

Échantillon

            // Using
            System.Collections.Generic.Dictionary<object, object> myDictionary = new System.Collections.Generic.Dictionary<object, object>();
            // myDictionary.Add(myObjectKey1, myObjectValue1); // Sample
            // myDictionary.Add(myObjectKey2, myObjectValue2); // Sample
            // myDictionary.Add(myObjectKey3, myObjectValue3); // Sample
            // myDictionary.Add(myObjectKey4, myObjectValue4); // Sample

            // var myShufledKeys = myDictionary.Shuffle(); // Sample
            // var myShufledValue = myDictionary[myShufledKeys[0]]; // Sample

            // Easy Sample
            var myObjects = System.Linq.Enumerable.Range(0, 4);
            foreach(int i in myObjects)
                myDictionary.Add(i, string.Format("myValueObjectNumber: {0}", i));

            var myShufledKeys = myDictionary.Shuffle();
            var myShufledValue = myDictionary[myShufledKeys[0]];
0
user3245067