Si prega di suggerire un modo più semplice per ottenere una raccolta casualmente casuale del conteggio "n" da una raccolta con elementi "N". dove n < = NQuery LINQ ottimale per ottenere una sottocartella casuale - Shuffle
risposta
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.
Shuffle la raccolta in un ordine casuale e prendere i primi articoli n
dal risultato.
Nota che non devi mescolare completamente la raccolta quando n
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];
}
}
}
+1 Linqy ed efficiente. Tanto di cappello :) –
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
@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
Ci scusiamo per il codice brutto :-), ma
var result =yourCollection.OrderBy(p => (p.GetHashCode().ToString() + Guid.NewGuid().ToString()).GetHashCode()).Take(n);
'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
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);
Questo non ha alcun pregiudizio casuale diverso da qualsiasi bias insignificante da 'Random' stesso. È anche molto efficiente. – Enigmativity
È venuto qui per suggerire questo. Questa dovrebbe essere la risposta accettata. –
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();
Stai sprecando memoria creando 'newList'. Forse prendere in considerazione l'uso di 'yield return' all'interno dell'istruzione' foreach'. – bradlis7
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
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);
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 –
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
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