.NET ile bir diziyi rastgele oluşturmanın en iyi yolu


141

Bir dizi dizeyi .NET ile rasgele seçmenin en iyi yolu nedir? Dizim yaklaşık 500 dize içeriyor Arrayve aynı dizelerle ancak rastgele bir sırayla yeni bir tane oluşturmak istiyorum .

Lütfen cevabınıza bir C # örneği ekleyin.


1
İşte bunun için garip ama basit bir çözüm - stackoverflow.com/a/4262134/1298685 .
Ian Campbell

1
MedallionRandom NuGet paketini kullanarak , bu sadece myArray.Shuffled().ToArray()(veya myArray.Shuffle()geçerli diziyi değiştirmek istiyorsanız)
ChaseMedallion

Yanıtlar:


171

.NET 3.5 kullanıyorsanız, aşağıdaki IEnumerable serinliğini kullanabilirsiniz (VB.NET, C # değil, ancak fikir açık olmalıdır ...):

Random rnd=new Random();
string[] MyRandomArray = MyArray.OrderBy(x => rnd.Next()).ToArray();    

Düzenleme: Tamam ve burada karşılık gelen VB.NET kodu:

Dim rnd As New System.Random
Dim MyRandomArray = MyArray.OrderBy(Function() rnd.Next()).ToArray()

İkinci düzenleme, zamana dayalı bir sıralamanın döndürülmesi nedeniyle System.Random "iş parçacığı güvenli değil" ve "yalnızca oyuncak uygulamaları için uygun değildir" ifadelerine yanıt olarak: Örneğimde kullanıldığı gibi, Random () mükemmel iş parçacığı için güvenli değilse, diziyi yeniden rastgele girdiğiniz rutinin yeniden girilmesine izin veriyorsunuz, bu durumda lock (MyRandomArray)verilerinizi bozmamak için yine de böyle bir şeye ihtiyacınız olacak ve bu da koruyacaktır rnd.

Ayrıca, entropi kaynağı olarak System.Random'un çok güçlü olmadığı iyi anlaşılmalıdır. MSDN belgelerinde belirtildiği gibi, System.Security.Cryptography.RandomNumberGeneratorgüvenlikle ilgili bir şey yapıyorsanız, türetilmiş bir şey kullanmalısınız . Örneğin:

using System.Security.Cryptography;

...

RNGCryptoServiceProvider rnd = new RNGCryptoServiceProvider();
string[] MyRandomArray = MyArray.OrderBy(x => GetNextInt32(rnd)).ToArray();

...

static int GetNextInt32(RNGCryptoServiceProvider rnd)
    {
        byte[] randomInt = new byte[4];
        rnd.GetBytes(randomInt);
        return Convert.ToInt32(randomInt[0]);
    }

iki not: 1) System.Random iş parçacığı için güvenli değildir (uyarıldınız) ve 2) System.Random zaman tabanlıdır, bu nedenle bu kodu yoğun bir eşzamanlı sistemde kullanırsanız, iki istek için aynı değer (yani
webapps'te

2
Yukarıdaki netleştirmek için, System.Random geçerli zamanı kullanarak kendisini tohum ve böylece, aynı "rasgele" sequence..System.Random üretecektir aynı anda yaratılmış iki durum sadece oyuncak uygulamalarda kullanılması gereken
therealhoff

8
Ayrıca bu algoritma O (n log n) ve Qsort algoritması tarafından önyargılıdır. O (n) yansız bir çözüm için cevabımı görün.
Matt Howells

9
OrderBySıralama anahtarlarını dahili olarak önbelleğe almadığı sürece , bunun aynı zamanda sıralı karşılaştırmaların geçiş özelliğini de ihlal etme sorunu vardır. OrderByDoğru sonuçları üreten bir hata ayıklama modu doğrulaması varsa, teoride bir istisna atabilir.
Sam Harwell


205

Aşağıdaki uygulamada Fisher-Yates algoritması AKA Knuth Shuffle kullanılmaktadır. O (n) zamanında çalışır ve yerinde karıştırır, bu nedenle daha fazla kod satırı olmasına rağmen 'rastgele sırala' tekniğinden daha iyi performans gösterir. Bazı karşılaştırmalı performans ölçümleri için buraya bakın . Şifresiz amaçlar için iyi olan System.Random'u kullandım. *

static class RandomExtensions
{
    public static void Shuffle<T> (this Random rng, T[] array)
    {
        int n = array.Length;
        while (n > 1) 
        {
            int k = rng.Next(n--);
            T temp = array[n];
            array[n] = array[k];
            array[k] = temp;
        }
    }
}

Kullanımı:

var array = new int[] {1, 2, 3, 4};
var rng = new Random();
rng.Shuffle(array);
rng.Shuffle(array); // different order from first call to Shuffle

* Daha uzun diziler için, (aşırı büyük) permütasyon sayısını eşit derecede mümkün kılmak için, her takas için yeterli entropi üretmek üzere birçok tekrarlama yoluyla bir sahte rasgele sayı üretecinin (PRNG) çalıştırılması gerekecektir. 500 elementlik bir dizi için olası 500'ün sadece çok küçük bir kısmı! bir PRNG kullanarak permütasyon elde etmek mümkün olacaktır. Bununla birlikte, Fisher-Yates algoritması tarafsızdır ve bu nedenle shuffle kullandığınız RNG kadar iyi olacaktır.


1
Parametreleri değiştirmek ve kullanımı array.Shuffle(new Random());.. gibi yapmak daha iyi olmaz mıydı ?
Ken Kin

Çerçeve 4.0 -> (dizi [n], dizi [k]) = (dizi [k], dizi [n]) itibariyle Tuples'ı kullanarak değiştirmeyi basitleştirebilirsiniz;
dynamichael

@Ken Kin: Hayır, bu kötü olur. Bunun nedeni, new Random()sadece her ~ 16ms'de bir güncellenen geçerli sistem zamanına göre bir tohum değeri ile başlatılmasıdır.
Matt Howells

Bu listeden removeAt çözümüne karşı bazı hızlı testlerde, 999 elementte küçük bir fark vardır. Fark 99999 rasgele ints ile sertleşir, bu çözelti 3ms'de ve diğeri 1810ms'de olur.
galamdring

18

Karıştırma algoritması mı arıyorsunuz?

Tamam, bunu yapmanın iki yolu var: zeki-ama-insanlar-her zaman-gibi-yanlış-anlama-ve-al-yanlış-yani-belki-onun-o-değil-her şeyden sonra yol ve aptal-kayalar-ama-kim-umursuyor-çünkü-işe yarıyor.

Aptalca yol

  • İlk dizinizin bir kopyasını oluşturun, ancak her dizeyi rastgele bir sayıyla etiketleyin.
  • Yinelenen diziyi rasgele sayıya göre sıralayın.

Bu algoritma iyi çalışıyor, ancak rastgele sayı üretecinizin aynı sayıda iki dizeyi etiketlemesinin olası olmadığından emin olun. Çünkü sözde Doğum Paradox , bu daha sık beklediğinizden daha olur. Zaman karmaşıklığı O ( n log n ) 'dir.

Akıllı yol

Bunu özyinelemeli bir algoritma olarak tanımlayacağım:

N boyutunda bir diziyi karıştırmak için ([0 .. n -1] aralığındaki dizinler ):

Eğer , n = 0
  • hiçbir şey yapma
eğer n > 0
  • (özyinelemeli adım) dizinin ilk n -1 öğelerini karıştır
  • [0 .. n -1] aralığında rastgele bir dizin seçin, x
  • dizin elemanı değiş tokuş n -1 indeksi en elemanı ile x

Yinelemeli eşdeğer, dizi boyunca bir yineleyiciyi yürüterek, ilerlerken rastgele öğelerle değiştirmektir, ancak yineleyicinin işaret ettiği öğeden sonra bir öğeyle değiştiremeyeceğinize dikkat edin. Bu çok yaygın bir hatadır ve taraflı bir karışıklığa yol açar.

Zaman karmaşıklığı O ( n ) 'dir.


8

Bu algoritma basittir ancak etkili değildir, O (N 2 ). Tüm "sıralama ölçütü" algoritmaları tipik olarak O (N log N) 'dir. Muhtemelen yüz binlerce elementin altında bir fark yaratmaz, ancak büyük listeler için olurdu.

var stringlist = ... // add your values to stringlist

var r = new Random();

var res = new List<string>(stringlist.Count);

while (stringlist.Count >0)
{
   var i = r.Next(stringlist.Count);
   res.Add(stringlist[i]);
   stringlist.RemoveAt(i);
}

O (N 2 ) 'nin ince olmasının nedeni : List.RemoveAt () , sondan sırayla kaldırmazsanız O (N) işlemidir.


2
Bu, bir knuth shuffle ile aynı etkiye sahiptir, ancak bir listeyi doldurmayı ve diğerini yeniden doldurmayı içerdiğinden, etkili değildir. Öğeleri yerinde değiştirmek daha iyi bir çözüm olacaktır.
Nick Johnson

1
Bu zarif ve kolayca anlaşılabilir buluyorum ve 500
telde

4

Ayrıca Matt Howells'den bir uzatma yöntemi de yapabilirsiniz. Misal.

   namespace System
    {
        public static class MSSystemExtenstions
        {
            private static Random rng = new Random();
            public static void Shuffle<T>(this T[] array)
            {
                rng = new Random();
                int n = array.Length;
                while (n > 1)
                {
                    int k = rng.Next(n);
                    n--;
                    T temp = array[n];
                    array[n] = array[k];
                    array[k] = temp;
                }
            }
        }
    }

Sonra sadece şöyle kullanabilirsiniz:

        string[] names = new string[] {
                "Aaron Moline1", 
                "Aaron Moline2", 
                "Aaron Moline3", 
                "Aaron Moline4", 
                "Aaron Moline5", 
                "Aaron Moline6", 
                "Aaron Moline7", 
                "Aaron Moline8", 
                "Aaron Moline9", 
            };
        names.Shuffle<string>();

Neden yöntemi her çağrıyı yeniden yaratıyorsunuz ... Sınıf düzeyinde beyan ediyorsunuz ama yerel olarak kullanıyorsunuz ...
Yaron

1

Bir dizi dizede gezinmek zorunda olduğunuz için diziyi rasgele yoğunlaştırır. Neden sadece diziden rastgele okunmuyor? En kötü durumda, getNextString () ile bir sarıcı sınıf bile oluşturabilirsiniz. Rastgele bir dizi oluşturmanız gerekiyorsa,

for i = 0 -> i= array.length * 5
   swap two strings in random places

* 5 isteğe bağlıdır.


Diziden rastgele bir okuma bazı öğeleri birden çok kez vurmak ve diğerleri özledim!
Ray Hayes

Karışık algoritması bozuk. Karıştırmanız tarafsız olmadan önce keyfi 5'inizi çok yüksek yapmanız gerekir.
Pitarou

(İnteger) dizinlerinden oluşan bir Array yapın. Dizinleri karıştırın. Sadece dizinleri rastgele sırada kullanın. Kopya yok, bellekteki dize başvurularının etrafında karışıklık yok (her biri stajyeyi tetikleyebilir ve neyi tetikleyemez).
Christopher

1

Sadece kafamın üstünü düşünerek bunu yapabilirsin:

public string[] Randomize(string[] input)
{
  List<string> inputList = input.ToList();
  string[] output = new string[input.Length];
  Random randomizer = new Random();
  int i = 0;

  while (inputList.Count > 0)
  {
    int index = r.Next(inputList.Count);
    output[i++] = inputList[index];
    inputList.RemoveAt(index);
  }

  return (output);
}

0

Aynı uzunlukta rastgele yüzer veya ints dizisi oluşturun. Bu diziyi sıralayın ve hedef dizinizde karşılık gelen takasları yapın.

Bu gerçekten bağımsız bir çeşittir.


0
Random r = new Random();
List<string> list = new List(originalArray);
List<string> randomStrings = new List();

while(list.Count > 0)
{
int i = r.Random(list.Count);
randomStrings.Add(list[i]);
list.RemoveAt(i);
}

0

Jacco, çözümün özel bir IComparer olması güvenli değil. Sıralama rutinleri, karşılaştırıcının düzgün çalışması için çeşitli gereksinimlere uymasını gerektirir. Bunlardan birincisi tutarlılık. Karşılaştırıcı aynı nesne çiftinde çağrılırsa, her zaman aynı sonucu döndürmelidir. (karşılaştırma da geçişli olmalıdır).

Bu gereksinimlerin karşılanmaması, sınırlama döngüsünde sonsuz döngü olasılığı da dahil olmak üzere herhangi bir sayıda soruna neden olabilir.

Her bir girişle rastgele bir sayısal değer ilişkilendiren ve daha sonra bu değere göre sıralanan çözümlerle ilgili olarak, iki girişe aynı sayısal değer atandığında, çıktının rastgele olması tehlikeye gireceğinden, bunlar çıktıda doğal bir sapmaya yol açar. ("Kararlı" bir sıralama rutininde, girişte hangisi çıkışta birinci olursa, Array.Sort kararlı olmaz, ancak Quicksort algoritması tarafından yapılan bölümlemeye dayalı bir önyargı vardır).

İstediğiniz rastgele seviyeyi düşünmeniz gerekir. Kararlı bir saldırgana karşı korumak için kriptografik rasgelelik düzeylerine ihtiyaç duyduğunuz bir poker sitesi çalıştırıyorsanız, bir şarkı çalma listesini rastgele seçmek isteyen birinden çok farklı gereksinimleriniz vardır.

Şarkı listesi karıştırma için, ekilmiş bir PRNG (System.Random gibi) kullanmakta sorun yoktur. Bir poker sitesi için, bu bir seçenek bile değildir ve sorunu yığın yığını akışında sizin için yapacaklarından çok daha zor düşünmeniz gerekir. (kriptografik bir RNG kullanmak sadece bir başlangıçtır, algoritmanızın bir önyargı vermemesini, yeterli entropi kaynaklarına sahip olduğunuzdan ve sonraki rastgele durumlardan ödün verebilecek herhangi bir iç durumu göstermediğinizden emin olmanız gerekir).


0

Bu gönderi zaten oldukça iyi yanıtlandı - hızlı ve tarafsız bir sonuç için Fisher-Yates shuffle'ın Durstenfeld uygulamasını kullanın. Hatta bazı uygulamalar yayınlanmıştır, ancak bazılarının aslında yanlış olduğunu not ediyorum.

Bu tekniği kullanarak tam ve kısmi karıştırmaları uygulamak hakkında bir süre önce birkaç yazı yazdım ve (bu ikinci bağlantı değer katmayı umduğum yer) ayrıca uygulamanızın tarafsız olup olmadığını nasıl kontrol edeceğiniz hakkında bir takip yazısı , herhangi bir karıştırma algoritmasını kontrol etmek için kullanılabilir. İkinci yazının sonunda rasgele sayı seçiminde basit bir hatanın etkisini görebiliyorsunuz.


1
Bağlantılarınız hala bozuk: /
Wai Ha Lee

0

Tamam, bu açıkça benim tarafımdan bir yumru (özür diler ...), ancak genellikle oldukça genel ve kriptografik olarak güçlü bir yöntem kullanıyorum.

public static class EnumerableExtensions
{
    static readonly RNGCryptoServiceProvider RngCryptoServiceProvider = new RNGCryptoServiceProvider();
    public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> enumerable)
    {
        var randomIntegerBuffer = new byte[4];
        Func<int> rand = () =>
                             {
                                 RngCryptoServiceProvider.GetBytes(randomIntegerBuffer);
                                 return BitConverter.ToInt32(randomIntegerBuffer, 0);
                             };
        return from item in enumerable
               let rec = new {item, rnd = rand()}
               orderby rec.rnd
               select rec.item;
    }
}

Shuffle (), herhangi bir IEnumerable'da bir uzantıdır, yani bir listede 0 ile 1000 arasındaki sayıların rasgele sırayla elde edilmesi

Enumerable.Range(0,1000).Shuffle().ToList()

Bu yöntem, sıralama konusunda herhangi bir sürpriz vermeyecektir, çünkü sıralama değeri dizideki eleman başına tam olarak bir kez üretilir ve hatırlanır.


0

Karmaşık algoritmalara ihtiyacınız yok.

Sadece basit bir satır:

Random random = new Random();
array.ToList().Sort((x, y) => random.Next(-1, 1)).ToArray();

Dönüştürdüğümüz gerektiğini Not Arraya Listkullanmak yoksa, ilk Listbaşta.

Ayrıca, bunun çok büyük diziler için verimli olmadığını unutmayın! Aksi takdirde temiz ve basit.


Hata: Operatör '.' 'void' türündeki işlenene uygulanamaz
faydalı

0

Bu, burada sağlanan örneğe dayanan eksiksiz bir çalışan Konsol çözümüdür :

class Program
{
    static string[] words1 = new string[] { "brown", "jumped", "the", "fox", "quick" };

    static void Main()
    {
        var result = Shuffle(words1);
        foreach (var i in result)
        {
            Console.Write(i + " ");
        }
        Console.ReadKey();
    }

   static string[] Shuffle(string[] wordArray) {
        Random random = new Random();
        for (int i = wordArray.Length - 1; i > 0; i--)
        {
            int swapIndex = random.Next(i + 1);
            string temp = wordArray[i];
            wordArray[i] = wordArray[swapIndex];
            wordArray[swapIndex] = temp;
        }
        return wordArray;
    }         
}

0
        int[] numbers = {0,1,2,3,4,5,6,7,8,9};
        List<int> numList = new List<int>();
        numList.AddRange(numbers);

        Console.WriteLine("Original Order");
        for (int i = 0; i < numList.Count; i++)
        {
            Console.Write(String.Format("{0} ",numList[i]));
        }

        Random random = new Random();
        Console.WriteLine("\n\nRandom Order");
        for (int i = 0; i < numList.Capacity; i++)
        {
            int randomIndex = random.Next(numList.Count);
            Console.Write(String.Format("{0} ", numList[randomIndex]));
            numList.RemoveAt(randomIndex);
        }
        Console.ReadLine();

-1

İşte OLINQ kullanmanın basit bir yolu:

// Input array
List<String> lst = new List<string>();
for (int i = 0; i < 500; i += 1) lst.Add(i.ToString());

// Output array
List<String> lstRandom = new List<string>();

// Randomize
Random rnd = new Random();
lstRandom.AddRange(from s in lst orderby rnd.Next(100) select s);

-2
private ArrayList ShuffleArrayList(ArrayList source)
{
    ArrayList sortedList = new ArrayList();
    Random generator = new Random();

    while (source.Count > 0)
    {
        int position = generator.Next(source.Count);
        sortedList.Add(source[position]);
        source.RemoveAt(position);
    }  
    return sortedList;
}

Bana göre, bir Array'ı ikinci bir Array bildirerek karıştırmaya çalışmak yerine hem verimliliği hem de okunabilirliği artırabileceğinizi hissediyorsunuz, bir Listeye, Shuffle'a ve bir sortedList = source.ToList().OrderBy(x => generator.Next()).ToArray();
Array'a
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.