2009-10-30 4 views

risposta

33

Un'altra opzione è quella di utilizzare OrderBy e per ordinare su un valore GUID, che è possibile farlo utilizzando:

var result = sequence.OrderBy(elem => Guid.NewGuid()); 

Ho fatto alcuni test empirici per convincermi che quanto sopra in realtà genera una distribuzione casuale (che appare fare). Puoi vedere i miei risultati allo Techniques for Randomly Reordering an Array.

+16

Questa soluzione viola il contratto di orderby, in particolare che un determinato oggetto ha una chiave coerente in tutto l'ordinamento. Se funziona, lo fa solo per caso, e potrebbe rompere le versioni future del framework. Per maggiori dettagli, vedi http://blogs.msdn.com/b/ericlippert/archive/2011/01/31/spot-the-defect-bad-comparisons-part-four.aspx –

+0

Buona cattura. Tuttavia, se questo è stato eseguito su una raccolta effettiva, ad esempio aggiungendo .ToList() o .ToArray() alla fine, non c'è alcuna possibilità che la raccolta venga elaborata/iterando codice Linq più di una volta per eseguire la ordinare. Immagino che sopravviverebbe anche agli aggiornamenti futuri visto che agirà come un'istantanea prevedibile. – MutantNinjaCodeMonkey

+5

Mi sono appena imbattuto in questa pagina e penso che sia geniale, ma non capisco il commento. cosa potrebbe andare storto nell'usare questo metodo per mescolare una collezione (non sto chiedendo che sarcasticamente, sono sinceramente curioso riguardo le possibilità)? – SelAromDotNet

6

Shuffle la raccolta in un ordine casuale e prendere i primi articoli n dal risultato.

+6

Nota che non devi mescolare completamente la raccolta quando n

98

seguito alla risposta del mquander e il commento di Dan Blanchard, ecco un metodo di estensione LINQ-friendly che esegue una Fisher-Yates-Durstenfeld shuffle:

// take n random items from yourCollection 
var randomItems = yourCollection.Shuffle().Take(n); 

// ... 

public static class EnumerableExtensions 
{ 
    public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source) 
    { 
     return source.Shuffle(new Random()); 
    } 

    public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random rng) 
    { 
     if (source == null) throw new ArgumentNullException("source"); 
     if (rng == null) throw new ArgumentNullException("rng"); 

     return source.ShuffleIterator(rng); 
    } 

    private static IEnumerable<T> ShuffleIterator<T>(
     this IEnumerable<T> source, Random rng) 
    { 
     var buffer = source.ToList(); 
     for (int i = 0; i < buffer.Count; i++) 
     { 
      int j = rng.Next(i, buffer.Count); 
      yield return buffer[j]; 

      buffer[j] = buffer[i]; 
     } 
    } 
} 
+3

+1 Linqy ed efficiente. Tanto di cappello :) –

+0

Sono io o questo metodo rovina l'indice della lista? Ogni volta che si usa elementAt() dopo aver mischiato il risultato che ottengo è totalmente inaspettato ... – Htbaa

+2

@Htbaa: il metodo restituisce una sequenza calcolata pigramente. Se esegui 'seq.Shuffle(). ElementAt (n)' più volte poi ti rimescoli ogni volta, quindi è probabile che otterrai un oggetto diverso in posizione 'n'. Se si desidera mescolare * una volta *, è necessario memorizzare la sequenza in una raccolta concreta di qualche tipo: ad esempio, 'var list = seq.Shuffle(). ToList()'. Quindi puoi fare 'list.ElementAt (n)' tutte le volte che vuoi - o semplicemente 'list [n]' - e otterrai sempre lo stesso oggetto. – LukeH

-3

Ci scusiamo per il codice brutto :-), ma

 

var result =yourCollection.OrderBy(p => (p.GetHashCode().ToString() + Guid.NewGuid().ToString()).GetHashCode()).Take(n); 
 
+0

'p.GetHashCode(). ToString() + Guid.NewGuid(). ToString()). GetHashCode()' non è casuale. Potrebbe sembrare casuale, ma non lo è. Dovresti usare un RNG per casualità - i dati sono stati progettati dagli scienziati per essere il più casuali possibile. – Enigmativity

10

Questo ha alcuni problemi con "bias casuale" e sono sicuro che non è ottimale, questa è un'altra possibilità:

var r = new Random(); 
l.OrderBy(x => r.NextDouble()).Take(n); 
+0

Questo non ha alcun pregiudizio casuale diverso da qualsiasi bias insignificante da 'Random' stesso. È anche molto efficiente. – Enigmativity

+0

È venuto qui per suggerire questo. Questa dovrebbe essere la risposta accettata. –

-1

Scrivo questo sovrascrive il metodo:

public static IEnumerable<T> Randomize<T>(this IEnumerable<T> items) where T : class 
{ 
    int max = items.Count(); 
    var secuencia = Enumerable.Range(1, max).OrderBy(n => n * n * (new Random()).Next()); 

    return ListOrder<T>(items, secuencia.ToArray()); 
} 

private static IEnumerable<T> ListOrder<T>(IEnumerable<T> items, int[] secuencia) where T : class 
     { 
      List<T> newList = new List<T>(); 
      int count = 0; 
      foreach (var seed in count > 0 ? secuencia.Skip(1) : secuencia.Skip(0)) 
      { 
       newList.Add(items.ElementAt(seed - 1)); 
       count++; 
      } 
      return newList.AsEnumerable<T>(); 
     } 

Poi, io ho la mia lista sorgente (tutte le voci)

var listSource = p.Session.QueryOver<Listado>(() => pl) 
         .Where(...); 

Infine, mi chiamano "Randomize" e ottengo un sub-raccolta casuale di articoli, nel mio caso, 5 articoli:

var SubCollection = Randomize(listSource.List()).Take(5).ToList(); 
+0

Stai sprecando memoria creando 'newList'. Forse prendere in considerazione l'uso di 'yield return' all'interno dell'istruzione' foreach'. – bradlis7

+0

La riga 'Enumerable.Range (1, max) .OrderBy (n => n * n * (new Random()). Next())' è terribile - non è affatto casuale. chiamare 'new Random()). Next()' come that restituirà lo stesso numero nella maggior parte dei casi e 'n * n' rende il numero di parte - e possibilmente potrebbe causare eccezioni di overflow. – Enigmativity

0

Un po 'meno casuale, ma efficiente:

var rnd = new Random(); 
var toSkip = list.Count()-n; 

if (toSkip > 0) 
    toSkip = rnd.Next(toSkip); 
else 
    toSkip=0; 

var randomlySelectedSequence = list.Skip(toSkip).Take(n);