2012-07-03 12 views
8

modulo di Python 'casuale' ha una funzione random.choiceEmula pitone NET

random.choice(seq)
Return un elemento casuale della sequenza non vuota seq. Se seq è vuoto, solleva IndexError.

Come posso emulare questo in .NET?

public T RandomChoice<T> (IEnumerable<T> source) 

Edit: ho sentito questa come una questione colloquio di qualche anno fa, ma oggi il problema si è verificato naturalmente nel mio lavoro. La questione intervista ha affermato con vincoli

  • 'la sequenza è troppo lungo per salvare in memoria'
  • 'si può solo loop all'interno di una sequenza di una volta'
  • 'la sequenza non ha una lunghezza/conteggio metodo '(à la. NET IEnumerable)
+0

Stai dicendo che vuoi una funzione che restituisca * esattamente ciò che Python fa *? O vuoi una funzione con * lo stesso contratto *? Cioè, saresti felice se la funzione .NET restituisse elementi diversi da quello che sarebbe Python? – AakashM

+2

Giusto per commentare le risposte fornite, @MattHickford, forse dovresti considerare, oltre a un 'IEnumerable' includere un sovraccarico' IList' (o fare un check in 'IEnumerable' se è un' IList') in modo che tu possa evitare di dover enumerare e creare una raccolta copiata. EDIT: Potresti anche aggiungere un overload 'params' per estrarre una lista fatta in fase di compilazione:' RandomChoice ("apple", "pera", "orange") ' –

+0

AakashM, chiedo un analogo .NET del Python funzione. Cos'è un contratto? –

risposta

12

Per creare un metodo che iterizza l'origine una sola volta e non è necessario allocare memoria per archiviarlo temporaneamente, si contano quanti elementi si sono iterati e si determina la probabilità che l'elemento corrente sia il risultato:

public T RandomChoice<T> (IEnumerable<T> source) { 
    Random rnd = new Random(); 
    T result = default(T); 
    int cnt = 0; 
    foreach (T item in source) { 
    cnt++; 
    if (rnd.Next(cnt) == 0) { 
     result = item; 
    } 
    } 
    return result; 
} 

Quando si è al primo punto, la probabilità è 1/1 che dovrebbe essere usato (come quello è l'unico elemento che avete visto fino a questo punto). Quando sei al secondo elemento, la probabilità è 1/2 che dovrebbe sostituire il primo elemento e così via.


Questo, naturalmente, usare un po 'di più CPU, in quanto crea un numero casuale per ogni articolo, non solo un singolo di numeri casuali per selezionare un elemento, come dasblinkenlight sottolineato. È possibile controllare se la fonte implementa IList<T>, come suggerito Dan Tao, e utilizzare un'implementazione che utilizza le funzionalità per ottenere la lunghezza degli elementi di raccolta e di accesso per indice:

public T RandomChoice<T> (IEnumerable<T> source) { 
    IList<T> list = source as IList<T>; 
    if (list != null) { 
    // use list.Count and list[] to pick an item by random 
    } else { 
    // use implementation above 
    } 
} 

Nota: Si dovrebbe prendere in considerazione l'invio del Random istanza nel metodo. Altrimenti si otterrà lo stesso seme casuale se si chiama il metodo due volte troppo vicino nel tempo, poiché il seme viene creato dall'ora corrente.


Il risultato di una prova, raccogliendo un numero da una matrice contenente 0 - 9, 1000000 volte, per mostrare che la distribuzione dei numeri scelti non è asimmetrica:

0: 100278 
1: 99519 
2: 99994 
3: 100327 
4: 99571 
5: 99731 
6: 100031 
7: 100429 
8: 99482 
9: 100638 
+2

Ah, bello. Ricordo di dover fare qualcosa del genere una volta! Il fatto è che penso che dovrai dimostrare ai matematici di convincere tutti i lettori che questo è corretto (non è intuitivo che sia corretto, almeno per me). –

+0

Suggerirei ancora di ottimizzare il caso in cui 'source' è un' IList ', poiché sarà ovviamente più veloce accedere a un singolo elemento in modo casuale per indice piuttosto che scorrere l'elenco se non è necessario. –

+2

+1 Molto bello! Ecco un [collegamento a una semplice prova per induzione] (http://propersubset.com/2010/04/choosing-random-elements.html) che questo algoritmo seleziona un elemento casuale con la probabilità di '1/N'. Potresti voler notare che questo algoritmo salva la memoria per la variabile temp a scapito dell'utilizzo di CPU aggiuntive per generare numeri casuali di 'N' invece di uno singolo. Non ha importanza con il normale RNG, ma l'uso di uno crittografico forte può trasformarlo in un compromesso. – dasblinkenlight

0

Bene, ottenere un elenco di tutti gli elementi nella sequenza. chiedere un generatore di numeri casuali per l'indice, restituire elemnt per indice. Definisci cos'è Sequence - IEnumerable sarebbe più ovvio, ma devi materializzarlo in una lista per conoscere il numero di elementi per il generatore di numeri casuali. Questo è btw., Non emulare, è implementare.

Questa è una domanda del corso di studio per principianti?

1
private static Random rng = new Random(); 

... 
return source.Skip(rng.next(source.Count())).Take(1); 
+2

1) Non dimenticare il blocco. 2) Il tuo codice richiede più enumerazioni di una sequenza, che in genere dovrebbero essere evitate. 3) 'Take (1)' restituisce una singola sequenza di elementi. Dovresti usare invece 'Primo()'. – CodesInChaos

6

Per evitare scorrendo la sequenza di due volte (una volta per il conteggio e una volta per l'elemento) è probabilmente una buona idea per salvare la sequenza in un array prima di ottenere il suo elemento casuale:

public static class RandomExt { 
    private static Random rnd = new Random(); 
    public static T RandomChoice<T> (this IEnumerable<T> source) { 
     var arr = source.ToArray(); 
     return arr[rnd.Next(arr.Length)]; 
    } 
    public static T RandomChoice<T> (this ICollection<T> source) { 
     return source[rnd.Next(rnd.Count)]; 
    } 
} 

EDIT Implementato a very good idea by Chris Sinclair.

+0

Mi piace particolarmente come metodo di estensione. +1 –

+0

Perché 'IList ' invece di 'ICollection '? – CodesInChaos

+0

@CodeInChaos Hai ragione, 'ICollection ' è più generale. – dasblinkenlight

1
 public T RandomChoice<T> (IEnumerable<T> source) 
     { 
      if (source == null) 
      { 
       throw new ArgumentNullException("source"); 
      } 

      var list = source.ToList(); 

      if (list.Count < 1) 
      { 
       throw new MissingMemberException(); 
      } 

      var rnd = new Random(); 
      return list[rnd.Next(0, list.Count)]; 
     } 

o estensione

public static T RandomChoice<T> (this IEnumerable<T> source) 
    { 
     if (source == null) 
     { 
      throw new ArgumentNullException("source"); 
     } 

     var list = source.ToList(); 

     if (list.Count < 1) 
     { 
      throw new MissingMemberException(); 
     } 

     var rnd = new Random(); 
     return list[rnd.Next(0, list.Count)]; 
    } 
1

mi piacerebbe andare con dasblinkenlight's answer, con un piccolo cambiamento: sfruttare il fatto che source potrebbe essere già una raccolta indicizzata, in questo caso davvero non c'è bisogno di compilare un nuovo array (o lista):

public static class RandomExt 
{ 
    public static T Choice<T>(this Random random, IEnumerable<T> sequence) 
    { 
     var list = sequence as IList<T> ?? sequence.ToList(); 
     return list[random.Next(list.Count)]; 
    } 
} 

Nota che ho anche modificato l'interfaccia dalla risposta di cui sopra per renderlo più coerente con la versione di Python si RIFERIMENTO nced nella tua domanda:

var random = new Random(); 
var numbers = new int[] { 1, 2, 3 }; 
int randomNumber = random.Choice(numbers); 

Edit: Mi piace Guffa's answer ancora meglio, in realtà.

0

Assumendo una ha un metodo di estensione IEnumerable.MinBy:

var r = new Random(); 
return source.MinBy(x=>r.Next()) 

il metodo MinBy non salva la sequenza di memoria, funziona come IEnumerable.Min fare uno iter azione (vedere MoreLinq o elsewhere)

+0

Questo non è molto diverso da ciò che @dasblinkenlight ha suggerito. Ciò comporta anche la creazione di un numero di numeri casuali e quindi la decisione su quando terminare (sebbene i controlli siano diversi in entrambi i casi). –