Domanda Inserimento casuale dal dizionario


Qual è il modo migliore per ottenere una voce casuale da un dizionario in c #?

Ho bisogno di ottenere un numero di oggetti casuali dal dizionario da visualizzare su una pagina, tuttavia non posso usare quanto segue poiché i dizionari non sono accessibili per indice:

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

Eventuali suggerimenti?


44
2018-06-22 16:24


origine


risposte:


Aggiornato per utilizzare i generici, essere ancora più veloce e con una spiegazione del motivo per cui questa opzione è più veloce.

Questa risposta è simile alle altre risposte, ma dal momento che hai detto che hai bisogno di "un numero di elementi casuali", questo sarà più performante:

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

Puoi usare questo metodo in questo modo:

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

Questo ha miglioramenti delle prestazioni rispetto alle altre risposte (inclusa la risposta di yshuditelu).

  1. Non è necessario creare una nuova raccolta di tutti gli elementi del dizionario ogni volta che si desidera recuperare un nuovo valore casuale. Questo è davvero un grosso problema se il tuo dizionario contiene molti elementi.
  2. Non è necessario eseguire una ricerca in base alla chiave del dizionario ogni volta che si recupera un valore casuale. Non è un grosso problema come il numero 1, ma è ancora due volte più veloce in questo modo.

I miei test mostrano che con 1000 oggetti nel dizionario, questo metodo va circa 70 volte più velocemente degli altri metodi suggeriti.


37
2018-06-22 16:56



Se stai usando .net 3.5, Enumerable ha un metodo di estensione ElementAt che ti permetterebbe di fare:

return dict.ElementAt(rand.Next(0, dict.Count)).Value;

39
2018-06-22 16:35



Dal tuo dizionario ...

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

puoi creare un completo elenco di chiavi...

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

e poi seleziona una chiave casuale dalla tua lista.

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

Quindi semplicemente restituire l'oggetto casuale corrispondente a quella chiave.

return dict[randomKey];

15
2018-06-22 16:39



La mia altra risposta è corretta per la domanda, e sarebbe utile in molti casi come ottenere informazioni sui rulli da dadi personalizzati (ogni tiro di dado è casuale, indipendente dagli altri dadi). Tuttavia, i tuoi commenti fanno sembrare che potresti sperare di ottenere una serie di elementi "unici" dal Dictionary, un po 'come trattare le carte da un mazzo. Una volta distribuita una carta, non vuoi più vedere la stessa carta fino a quando non ri-shuffle. In tal caso, la migliore strategia dipenderà esattamente da quello che stai facendo.

Se stai ottenendo solo alcuni elementi da un grande Dictionary, quindi dovresti essere in grado di adattare la mia altra risposta, rimuovendo l'elemento casuale dalla lista ogni volta che ne viene recuperata una nuova. Probabilmente vorrai anche fare la lista in a LinkedList, perché anche se sarà più lento trovare un elemento dal suo indice, è molto meno costoso rimuovere gli elementi dal centro di esso. Il codice per questo sarebbe un po 'più complicato, quindi se sei disposto a sacrificare alcune prestazioni per semplicità puoi semplicemente fare questo:

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

Se, d'altra parte, hai intenzione di estrarre un numero significativo di elementi dal tuo dizionario (cioè distribuire più del log (n) del tuo "mazzo"), starai meglio a mischiare l'intero mazzo prima e poi tirando dall'alto:

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

Il merito va a ookii.org per il semplice codice di mescolamento. Se questo non è ancora quello che stavi cercando, forse puoi iniziare una nuova domanda con maggiori dettagli su ciò che stai cercando di fare.


11
2018-06-24 19:34



Qualcosa di simile a:

Random rand = new Random();
Dictionary dict = GetDictionary();
var k = dict.Keys.ToList()[rand.Next(dict.Count)];
return dict[k];

3
2018-06-22 16:26



Questo non sarà terribilmente veloce, ma dovrebbe funzionare:

Random rand = new Random();
Dictionary dict = GetDictionary();
return dict.Skip(rand.Next(dict.Count)).First().Value;

2
2018-06-22 16:35



Una soluzione facile sarebbe usare il ToList() metodo di estensione e utilizzare l'indice della lista.

Se hai solo bisogno dei valori o delle chiavi (non della coppia chiave / valore) restituisci queste raccolte dal dizionario e usale ToList() anche.

        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
2018-06-22 16:31



Credo che l'unico modo sia creare prima un elenco separato di KeyValuePairs.


0
2018-06-22 16:29