C # 'daki genel listenin sırasını rastgele seçmenin en iyi yolu nedir? Bir piyango tipi uygulama için onları çizmek için, rastgele bir sipariş atamak istiyorum bir listede 75 sayı sonlu bir set var.
C # 'daki genel listenin sırasını rastgele seçmenin en iyi yolu nedir? Bir piyango tipi uygulama için onları çizmek için, rastgele bir sipariş atamak istiyorum bir listede 75 sayı sonlu bir set var.
Yanıtlar:
Herhangi karıştır (I)List
dayalı bir uzantısı yöntemi ile Fisher-Yates shuffle :
private static Random rng = new Random();
public static void Shuffle<T>(this IList<T> list)
{
int n = list.Count;
while (n > 1) {
n--;
int k = rng.Next(n + 1);
T value = list[k];
list[k] = list[n];
list[n] = value;
}
}
Kullanımı:
List<Product> products = GetProducts();
products.Shuffle();
Yukarıdaki kod takas adaylarını seçmek için çok eleştirilen System.Random yöntemini kullanır. Hızlı ama olması gerektiği kadar rastgele değil. Karıştırmalarınızda daha iyi bir rasgelelik kalitesine ihtiyacınız varsa System.Security'de rasgele sayı üretecini kullanın.
using System.Security.Cryptography;
...
public static void Shuffle<T>(this IList<T> list)
{
RNGCryptoServiceProvider provider = new RNGCryptoServiceProvider();
int n = list.Count;
while (n > 1)
{
byte[] box = new byte[1];
do provider.GetBytes(box);
while (!(box[0] < n * (Byte.MaxValue / n)));
int k = (box[0] % n);
n--;
T value = list[k];
list[k] = list[n];
list[n] = value;
}
}
Bu blogda basit bir karşılaştırma var (WayBack Machine).
Edit: Bu cevabı birkaç yıl önce yazdığından beri, birçok kişi benim karşılaştırmamdaki büyük aptal kusura işaret etmek için yorum yaptı veya bana yazdı. Tabii ki haklılar. System.Random'da amaçlandığı gibi kullanılırsa yanlış bir şey yoktur. Yukarıdaki ilk örneğimde, shngle yönteminin içindeki rng değişkenini somutlaştırıyorum; bu yöntem, yöntemin tekrar tekrar çağrılıp çağrılmayacağını soruyor. Aşağıda, bugün SO tarafından @weston'dan alınan gerçekten yararlı bir yoruma dayanan sabit, tam bir örnek bulunmaktadır.
program.cs:
using System;
using System.Collections.Generic;
using System.Threading;
namespace SimpleLottery
{
class Program
{
private static void Main(string[] args)
{
var numbers = new List<int>(Enumerable.Range(1, 75));
numbers.Shuffle();
Console.WriteLine("The winning numbers are: {0}", string.Join(", ", numbers.GetRange(0, 5)));
}
}
public static class ThreadSafeRandom
{
[ThreadStatic] private static Random Local;
public static Random ThisThreadsRandom
{
get { return Local ?? (Local = new Random(unchecked(Environment.TickCount * 31 + Thread.CurrentThread.ManagedThreadId))); }
}
}
static class MyExtensions
{
public static void Shuffle<T>(this IList<T> list)
{
int n = list.Count;
while (n > 1)
{
n--;
int k = ThreadSafeRandom.ThisThreadsRandom.Next(n + 1);
T value = list[k];
list[k] = list[n];
list[n] = value;
}
}
}
}
Random rng = new Random();
bir static
karşılaştırma yapmak karşılaştırma sonrası sorunu çözmek. Her bir sonraki çağrı önceki çağrılardan sonra olacağı gibi son rastgele sonuç.
Sadece öğeleri tamamen rastgele bir sırayla karıştırmamız gerekiyorsa (sadece bir listedeki öğeleri karıştırmak için), öğeleri guid ile sipariş eden bu basit ama etkili kodu tercih ederim ...
var shuffledcards = cards.OrderBy(a => Guid.NewGuid()).ToList();
var shuffledcards = cards.OrderBy(a => rng.Next());
compilr.com/grenade/sandbox/Program.cs
NewGuid
sadece size benzersiz bir GUID vereceğini garanti eder. Rasgelelik konusunda hiçbir garanti vermez. Benzersiz bir değer oluşturmaktan başka bir amaç için bir GUID kullanıyorsanız , bunu yanlış yapıyorsunuz demektir.
Burada bu basit algoritmanın tüm hantal versiyonları beni biraz şaşırttı. Fisher-Yates (veya Knuth shuffle) biraz zor ama çok kompakt. Neden zor? Çünkü rastgele sayı üretecinizin kapsayıcı veya münhasır r(a,b)
olduğu yerde değer döndürüp döndürmediğine dikkat etmeniz gerekir b
. Ayrıca Wikipedia açıklamasını da düzenledim, böylece insanlar orada sahte kodları körü körüne takip etmiyor ve hataları tespit etmek zor oluyor. .Net için, daha fazla uzatmadan Random.Next(a,b)
hariç sayıyı döndürür b
, C # / 'da nasıl uygulanabilir?
public static void Shuffle<T>(this IList<T> list, Random rnd)
{
for(var i=list.Count; i > 0; i--)
list.Swap(0, rnd.Next(0, i));
}
public static void Swap<T>(this IList<T> list, int i, int j)
{
var temp = list[i];
list[i] = list[j];
list[j] = temp;
}
i = list.Count - 1
, yani son yineleme rnd.Next(i, list.Count)
size geri vereceğim. Bu nedenle i < list.Count -1
döngü koşulu olarak ihtiyacınız vardır . Peki, buna ihtiyacınız yok, ama 1 iterasyon tasarrufu sağlıyor;)
IEnumerable için genişletme yöntemi:
public static IEnumerable<T> Randomize<T>(this IEnumerable<T> source)
{
Random rnd = new Random();
return source.OrderBy<T, int>((item) => rnd.Next());
}
OrderBy
öğeleri (görünüşte rastgele) tuşlarına göre sıralamak için bir QuickSort varyantı kullanır. QuickSort performansı O (N log N) ; aksine, bir Fisher-Yates karıştırması O (N) 'dir . 75 öğeden oluşan bir koleksiyon için bu çok önemli olmayabilir, ancak fark daha büyük koleksiyonlar için telaffuz edilecektir.
Random.Next()
bir değerler makul sözde rasgele dağılım üretebilir, ancak yok değil değerlerin benzersiz olacağını garanti. Yinelenen şifreler olasılığı ile (doğrusal olmayan) büyür N zaman kesin ulaşana kadar K 2 ^ 32 ± 1 ulaşır. OrderBy
QuickSort a, kararlı bir sıralama; bu nedenle, birden fazla elemanın aynı sahte rastgele indeks değeri atanması durumunda, çıkış dizisindeki sıraları giriş dizisindeki ile aynı olacaktır ; böylece "karıştırma" ya bir sapma eklenir.
Fikir, öğe ve rastgele düzen ile anonim bir nesne almak ve daha sonra bu sipariş ve dönüş değerine göre öğeleri yeniden düzenlemek:
var result = items.Select(x => new { value = x, order = rnd.Next() })
.OrderBy(x => x.order).Select(x => x.value).ToList()
public static List<T> Randomize<T>(List<T> list)
{
List<T> randomizedList = new List<T>();
Random rnd = new Random();
while (list.Count > 0)
{
int index = rnd.Next(0, list.Count); //pick a random item from the master list
randomizedList.Add(list[index]); //place it at the end of the randomized list
list.RemoveAt(index);
}
return randomizedList;
}
var listCopy = list.ToList()
Gelen listesindeki tüm öğeleri patlatmaktan kaçınmak istemiyor musunuz? Bu listeleri neden boşaltmak istediğinizi gerçekten anlamıyorum.
DÜZENLEMERemoveAt
benim önceki sürümde bir zayıflıktır. Bu çözüm bunun üstesinden gelir.
public static IEnumerable<T> Shuffle<T>(
this IEnumerable<T> source,
Random generator = null)
{
if (generator == null)
{
generator = new Random();
}
var elements = source.ToArray();
for (var i = elements.Length - 1; i >= 0; i--)
{
var swapIndex = generator.Next(i + 1);
yield return elements[swapIndex];
elements[swapIndex] = elements[i];
}
}
İsteğe bağlı olarak Random generator
, temel çerçeve uygulaması Random
iş parçacığı açısından güvenli veya gereksinimleriniz için kriptografik olarak yeterince güçlü değilse, uygulamanızı işleme enjekte edebilirsiniz.
İşte bir fikir, IList'i (umarım) verimli bir şekilde genişletmek.
public static IEnumerable<T> Shuffle<T>(this IList<T> list)
{
var choices = Enumerable.Range(0, list.Count).ToList();
var rng = new Random();
for(int n = choices.Count; n > 1; n--)
{
int k = rng.Next(n);
yield return list[choices[k]];
choices.RemoveAt(k);
}
yield return list[choices[0]];
}
GetNext
yoksa Next
?
Bu basit uzantı yöntemini kullanarak bunu başarabilirsiniz
public static class IEnumerableExtensions
{
public static IEnumerable<t> Randomize<t>(this IEnumerable<t> target)
{
Random r = new Random();
return target.OrderBy(x=>(r.Next()));
}
}
ve aşağıdakileri yaparak kullanabilirsiniz
// use this on any collection that implements IEnumerable!
// List, Array, HashSet, Collection, etc
List<string> myList = new List<string> { "hello", "random", "world", "foo", "bar", "bat", "baz" };
foreach (string s in myList.Randomize())
{
Console.WriteLine(s);
}
Random
Sınıf örneğini bir static
değişken olarak fonksiyonun dışında tutacağım . Aksi takdirde, hızlı bir şekilde arka arkaya çağrılırsa zamanlayıcıdan aynı randomizasyon tohumunu alabilirsiniz.
Bu, orijinali değiştirmemek istendiğinde tercih ettiğim bir karıştırma yöntemidir. Numaralandırılabilir bir dizi üzerinde çalışan Fisher – Yates "içten dışa" algoritmasının bir varyantıdır (uzunluğunun source
başlangıçtan itibaren bilinmesi gerekmez).
public static IList<T> NextList<T>(this Random r, IEnumerable<T> source)
{
var list = new List<T>();
foreach (var item in source)
{
var i = r.Next(list.Count + 1);
if (i == list.Count)
{
list.Add(item);
}
else
{
var temp = list[i];
list[i] = item;
list.Add(temp);
}
}
return list;
}
Bu algoritma, - arasında bir aralık tahsis edilerek de uygulanabilir 0
.length - 1
ve rasgele tüm endeksler tam bir kez seçilmiştir kadar geçen endeks ile rastgele seçilmiş endeksi değiştirerek endeks yorucu. Yukarıdaki kod, aynı şeyi yerine getirir, ancak ek ayırma olmadan. Ki bu oldukça düzgün.
Random
Sınıf ile ilgili olarak genel amaçlı bir sayı üreteci (ve bir piyango çalıştırıyor olsaydım farklı bir şey kullanmayı düşünürdüm). Ayrıca varsayılan olarak zamana dayalı bir tohum değerine dayanır. Sorunun küçük bir hafifletilmesi, Random
sınıfın tohumlanmasıdır RNGCryptoServiceProvider
veya RNGCryptoServiceProvider
tekdüze seçilmiş rastgele çift kayan nokta değerleri oluşturmak için buna benzer bir yöntemde kullanabilirsiniz (aşağıya bakın), ancak piyangoyu çalıştırmak rastgele ve doğanın anlaşılmasını gerektirir. rastgelelik kaynağı.
var bytes = new byte[8];
_secureRng.GetBytes(bytes);
var v = BitConverter.ToUInt64(bytes, 0);
return (double)v / ((double)ulong.MaxValue + 1);
Rastgele bir çift oluşturma noktası (sadece 0 ile 1 arasında) bir tamsayı çözeltisine ölçeklendirmek için kullanılır. Her x
zaman olacağı rastgele bir çifte dayalı bir listeden bir şey seçmeniz gerekiyorsa 0 <= x && x < 1
, düz ileri.
return list[(int)(x * list.Count)];
Zevk almak!
Eğer ikisini kullanmak sakıncası yoksa Lists
, bu muhtemelen bunu yapmanın en kolay yoludur, ancak muhtemelen en verimli veya tahmin edilemez olanı değildir:
List<int> xList = new List<int>() { 1, 2, 3, 4, 5 };
List<int> deck = new List<int>();
foreach (int xInt in xList)
deck.Insert(random.Next(0, deck.Count + 1), xInt);
Sabit bir sayınız varsa (75), 75 öğeden oluşan bir dizi oluşturabilir, ardından listenizi numaralandırarak öğeleri dizideki rastgele konumlara taşıyabilirsiniz. Fisher-Yates shuffle'ı kullanarak liste numarasının dizi dizinine eşlenmesini oluşturabilirsiniz .
Genellikle kullanıyorum:
var list = new List<T> ();
fillList (list);
var randomizedList = new List<T> ();
var rnd = new Random ();
while (list.Count != 0)
{
var index = rnd.Next (0, list.Count);
randomizedList.Add (list [index]);
list.RemoveAt (index);
}
List<T> OriginalList = new List<T>();
List<T> TempList = new List<T>();
Random random = new Random();
int length = OriginalList.Count;
int TempIndex = 0;
while (length > 0) {
TempIndex = random.Next(0, length); // get random value between 0 and original length
TempList.Add(OriginalList[TempIndex]); // add to temp list
OriginalList.RemoveAt(TempIndex); // remove from original list
length = OriginalList.Count; // get new list <T> length.
}
OriginalList = new List<T>();
OriginalList = TempList; // copy all items from temp list to original list.
İşte karıştırılmış değerlerin bir bayt dizisini döndüren verimli bir Shuffler. Asla gereğinden fazla karıştırmaz. Daha önce kaldığı yerden yeniden başlatılabilir. Gerçek uygulamam (gösterilmiyor), kullanıcının belirlediği bir yedek karıştırıcıya izin veren bir MEF bileşenidir.
public byte[] Shuffle(byte[] array, int start, int count)
{
int n = array.Length - start;
byte[] shuffled = new byte[count];
for(int i = 0; i < count; i++, start++)
{
int k = UniformRandomGenerator.Next(n--) + start;
shuffled[i] = array[k];
array[k] = array[start];
array[start] = shuffled[i];
}
return shuffled;
}
'
İşte bunu yapmanın güvenli bir yolu:
public static class EnumerableExtension
{
private static Random globalRng = new Random();
[ThreadStatic]
private static Random _rng;
private static Random rng
{
get
{
if (_rng == null)
{
int seed;
lock (globalRng)
{
seed = globalRng.Next();
}
_rng = new Random(seed);
}
return _rng;
}
}
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> items)
{
return items.OrderBy (i => rng.Next());
}
}
public Deck(IEnumerable<Card> initialCards)
{
cards = new List<Card>(initialCards);
public void Shuffle()
}
{
List<Card> NewCards = new List<Card>();
while (cards.Count > 0)
{
int CardToMove = random.Next(cards.Count);
NewCards.Add(cards[CardToMove]);
cards.RemoveAt(CardToMove);
}
cards = NewCards;
}
public IEnumerable<string> GetCardNames()
{
string[] CardNames = new string[cards.Count];
for (int i = 0; i < cards.Count; i++)
CardNames[i] = cards[i].Name;
return CardNames;
}
Deck deck1;
Deck deck2;
Random random = new Random();
public Form1()
{
InitializeComponent();
ResetDeck(1);
ResetDeck(2);
RedrawDeck(1);
RedrawDeck(2);
}
private void ResetDeck(int deckNumber)
{
if (deckNumber == 1)
{
int numberOfCards = random.Next(1, 11);
deck1 = new Deck(new Card[] { });
for (int i = 0; i < numberOfCards; i++)
deck1.Add(new Card((Suits)random.Next(4),(Values)random.Next(1, 14)));
deck1.Sort();
}
else
deck2 = new Deck();
}
private void reset1_Click(object sender, EventArgs e) {
ResetDeck(1);
RedrawDeck(1);
}
private void shuffle1_Click(object sender, EventArgs e)
{
deck1.Shuffle();
RedrawDeck(1);
}
private void moveToDeck1_Click(object sender, EventArgs e)
{
if (listBox2.SelectedIndex >= 0)
if (deck2.Count > 0) {
deck1.Add(deck2.Deal(listBox2.SelectedIndex));
}
RedrawDeck(1);
RedrawDeck(2);
}
Yerinde çalışmak yerine yeni bir liste döndüren ve diğer birçok Linq yönteminin yaptığı gibi daha genel olan kabul edilen cevabın basit bir şekilde değiştirilmesi IEnumerable<T>
.
private static Random rng = new Random();
/// <summary>
/// Returns a new list where the elements are randomly shuffled.
/// Based on the Fisher-Yates shuffle, which has O(n) complexity.
/// </summary>
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> list) {
var source = list.ToList();
int n = source.Count;
var shuffled = new List<T>(n);
shuffled.AddRange(source);
while (n > 1) {
n--;
int k = rng.Next(n + 1);
T value = shuffled[k];
shuffled[k] = shuffled[n];
shuffled[n] = value;
}
return shuffled;
}
İnternette ilginç bir çözüm buldum.
Nezaket: https://improveandrepeat.com/2018/08/a-simple-way-to-shuffle-your-lists-in-c/
var shuffled = myList.OrderBy (x => Guid.NewGuid ()). ToList ();
Eski yazı kesin, ama ben sadece bir GUID kullanın.
Items = Items.OrderBy(o => Guid.NewGuid().ToString()).ToList();
Bir GUID her zaman benzersizdir ve sonuç her seferinde değiştiğinde yeniden oluşturulur.
Bu tür bir soruna çok basit bir yaklaşım, listede bir dizi rastgele eleman takası kullanmaktır.
Sözde kodda bu şöyle görünecektir:
do
r1 = randomPositionInList()
r2 = randomPositionInList()
swap elements at index r1 and index r2
for a certain number of times