วิธีที่ดีที่สุดในการสุ่มอาเรย์ด้วย. NET


141

วิธีที่ดีที่สุดในการสุ่มอาเรย์ของสตริงด้วย. NET คืออะไร? อาร์เรย์ของฉันมีสตริงประมาณ 500 รายการและฉันต้องการสร้างใหม่Arrayด้วยสตริงเดียวกัน แต่เรียงตามลำดับแบบสุ่ม

กรุณาใส่ตัวอย่าง C # ในคำตอบของคุณ


1
นี่เป็นวิธีการแก้ปัญหาที่แปลก แต่ง่ายสำหรับการนี้ - stackoverflow.com/a/4262134/1298685
Ian Campbell

1
ใช้แพคเกจMedallionRandom NuGet นี่เป็นเพียงแค่myArray.Shuffled().ToArray()(หรือmyArray.Shuffle()ถ้าคุณต้องการที่จะกลายพันธุ์อาร์เรย์ปัจจุบัน)
ChaseMedallion

คำตอบ:


171

หากคุณใช้. NET 3.5 คุณสามารถใช้ IEnumerable coolness ต่อไปนี้ (VB.NET ไม่ใช่ C # แต่ความคิดควรชัดเจน ... ):

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

แก้ไข: ตกลงและนี่คือรหัส VB.NET ที่สอดคล้องกัน:

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

การแก้ไขครั้งที่สองในการตอบสนองต่อข้อสังเกตว่า System.Random "ไม่ใช่ threadsafe" และ "เหมาะสำหรับแอพของเล่น" เนื่องจากการส่งคืนลำดับตามเวลา: ตามที่ใช้ในตัวอย่างของฉัน Random () นั้นปลอดภัยต่อเธรดอย่างสมบูรณ์เว้นแต่ คุณอนุญาตให้รูทีนที่คุณสุ่มอาร์เรย์ให้ป้อนซ้ำอีกครั้งซึ่งในกรณีนี้คุณจะต้องการอะไรเช่นนั้น lock (MyRandomArray)ต่อไปเพื่อไม่ให้ข้อมูลของคุณเสียหายซึ่งจะป้องกันrndเช่นกัน

นอกจากนี้ควรเข้าใจว่า System.Random ในฐานะที่เป็นแหล่งของเอนโทรปีนั้นไม่ค่อยแข็งแกร่งนัก ดังที่ระบุไว้ในเอกสาร MSDNคุณควรใช้สิ่งที่ได้มาจากSystem.Security.Cryptography.RandomNumberGeneratorหากคุณทำสิ่งใดก็ตามที่เกี่ยวข้องกับความปลอดภัย ตัวอย่างเช่น:

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]);
    }

หมายเหตุสองประการ: 1) System.Random ไม่ปลอดภัย (คุณได้รับการเตือน) และ 2) System.Random ขึ้นอยู่กับเวลาดังนั้นหากคุณใช้รหัสนี้ในระบบที่เกิดขึ้นพร้อมกันอย่างหนักเป็นไปได้ที่สองคำขอจะได้รับ มูลค่าเดียวกัน (เช่นใน webapps)
therealhoff

2
เพียงชี้แจงข้างต้นจะ System.Random เมล็ดพันธุ์ตัวเองใช้เวลาปัจจุบันดังนั้นสองกรณีที่สร้างขึ้นพร้อมกันจะสร้างเดียวกัน "สุ่ม" sequence..System.Random ควรจะใช้เฉพาะในแอพพลิเคของเล่น
therealhoff

8
อัลกอริทึมนี้คือ O (n log n) และเอนเอียงโดยอัลกอริทึม Qsort ดูคำตอบของฉันสำหรับการแก้ปัญหาที่เป็นกลาง (O)
Matt Howells

9
นอกจากว่าจะOrderByเก็บคีย์การเรียงข้อมูลภายในสิ่งนี้ยังมีปัญหาในการละเมิดคุณสมบัติสกรรมกริยาของการเปรียบเทียบคำสั่ง หากมีการตรวจสอบโหมดดีบักที่เคยให้OrderByผลลัพธ์ที่ถูกต้องในทางทฤษฎีแล้วมันอาจทำให้เกิดข้อยกเว้น
Sam Harwell


205

การใช้งานต่อไปนี้ใช้อัลกอริทึม Fisher-Yates AKA the Knuth Shuffle มันทำงานในเวลา O (n) และมีการสลับสับเปลี่ยนดังนั้นประสิทธิภาพที่ดีกว่าเทคนิค 'เรียงลำดับโดยการสุ่ม' แม้ว่าจะเป็นบรรทัดของโค้ดมากกว่า ดูที่นี่สำหรับการวัดประสิทธิภาพเชิงเปรียบเทียบ ฉันใช้ System.Random ซึ่งใช้ได้สำหรับจุดประสงค์ที่ไม่ใช่การเข้ารหัส *

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;
        }
    }
}

การใช้งาน:

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

* สำหรับอาร์เรย์ที่ยาวกว่าเพื่อที่จะทำให้การเรียงสับเปลี่ยน (มีขนาดใหญ่มาก) มีความเป็นไปได้อย่างเท่าเทียมกันมันก็จำเป็นที่จะต้องเรียกใช้ตัวสร้างตัวเลขสุ่มหลอก (PRNG) ผ่านการวนซ้ำจำนวนมากสำหรับการแลกเปลี่ยนแต่ละครั้ง สำหรับอาร์เรย์ 500 องค์ประกอบมีเพียงส่วนน้อยมากที่เป็นไปได้ 500 รายการ! การเรียงสับเปลี่ยนจะเป็นไปได้ที่จะได้รับโดยใช้ PRNG อย่างไรก็ตามอัลกอริทึม Fisher-Yates นั้นไม่เอนเอียงดังนั้นการสับเปลี่ยนจะดีเท่า RNG ที่คุณใช้


1
จะเป็นการดีกว่าหรือที่จะเปลี่ยนพารามิเตอร์และทำให้การใช้งานเป็นไปอย่างนั้นarray.Shuffle(new Random());..
Ken Kin

คุณสามารถทำให้การแลกเปลี่ยนง่ายขึ้นโดยใช้ Tuples ตั้งแต่ Framework 4.0 -> (array [n], array [k]) = (array [k], array [n]);
dynamichael

@Ken Kin: ไม่มันจะไม่ดี เหตุผลคือการnew Random()เริ่มต้นด้วยค่าเมล็ดขึ้นอยู่กับเวลาของระบบปัจจุบันซึ่งปรับปรุงเพียง ~ 16ms ทุก
Matt Howells

ในการทดสอบอย่างรวดเร็วของโซลูชัน vs รายการนี้มีความแตกต่างเล็กน้อยที่ 999 องค์ประกอบ ความแตกต่างนั้นรุนแรงมากที่ 99999 random ints ด้วยวิธีนี้ที่ 3ms และอื่น ๆ ที่ 1810ms
galamdring

18

คุณกำลังมองหาอัลกอริทึมแบบสับได้ใช่ไหม

โอเคมีสองวิธีในการทำสิ่งนี้: คนฉลาด แต่คนที่ดูเหมือนจะเข้าใจผิดเสมอและเข้าใจผิดผิดดังนั้นอาจเป็นไปได้ วิธีและวิธีโง่ - แต่ - ใคร - ใส่ใจ - เพราะ - มัน - ทำงาน

วิธีโง่

  • สร้างซ้ำอาร์เรย์แรกของคุณ แต่แท็กแต่ละสตริงควรมีตัวเลขสุ่ม
  • เรียงลำดับอาร์เรย์ที่ซ้ำกันตามตัวเลขสุ่ม

อัลกอริทึมนี้ใช้งานได้ดี แต่ตรวจสอบให้แน่ใจว่าตัวสร้างตัวเลขสุ่มของคุณไม่น่าจะติดแท็กสองสตริงด้วยหมายเลขเดียวกัน เนื่องจากการที่เรียกว่าBirthday Paradoxสิ่งนี้เกิดขึ้นบ่อยกว่าที่คุณคาดไว้ ความซับซ้อนของเวลาคือ O ( n log n )

วิธีที่ฉลาด

ฉันจะอธิบายสิ่งนี้เป็นอัลกอริทึมแบบเรียกซ้ำ:

วิธีสลับอาเรย์ของขนาดn (ดัชนีในช่วง [0 .. n -1]):

ถ้าn = 0
  • ไม่ทำอะไร
ถ้าn > 0
  • (ขั้นตอนแบบเรียกซ้ำ)สับเปลี่ยนnแรกองค์ประกอบ -1ของอาร์เรย์
  • เลือกดัชนีแบบสุ่ม, x , ในช่วง [0 .. n -1]
  • สลับองค์ประกอบที่ index n -1 ด้วยองค์ประกอบที่ index x

การทำซ้ำที่เทียบเท่าคือการเดินตัววนซ้ำผ่านอาร์เรย์สลับกับองค์ประกอบสุ่มตามที่คุณไป แต่สังเกตว่าคุณไม่สามารถสลับกับองค์ประกอบหลังจากที่ตัววนซ้ำชี้ไปที่ นี่เป็นข้อผิดพลาดทั่วไปและนำไปสู่การสับเปลี่ยนแบบลำเอียง

ความซับซ้อนของเวลาคือ O ( n )


8

อัลกอริทึมนี้ง่าย แต่ไม่มีประสิทธิภาพ O (N) 2 ) อัลกอริทึม "เรียงตาม" ทั้งหมดโดยทั่วไปคือ O (N log N) มันอาจไม่สร้างความแตกต่างด้านล่างองค์ประกอบนับแสน แต่จะเป็นรายการใหญ่

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 ) บอบบาง: List.RemoveAt ()เป็นการดำเนินการ O (N) เว้นแต่คุณจะลบออกจากลำดับ


2
สิ่งนี้มีผลเช่นเดียวกับการสับแบบ Knuth แต่มันไม่ได้มีประสิทธิภาพเนื่องจากมันเกี่ยวข้องกับการลดระดับรายการหนึ่งรายการและเปลี่ยนรายการใหม่ การแลกเปลี่ยนไอเท็มจะเป็นทางออกที่ดีกว่า
Nick Johnson

1
ฉันพบนี้สง่างามและเข้าใจได้ง่ายและ 500 สายมันไม่ได้ทำให้บิตของความแตกต่าง ...
Sklivvz

4

คุณยังสามารถสร้างวิธีการขยายเพิ่มเติมได้จาก Matt Howells ตัวอย่าง.

   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;
                }
            }
        }
    }

จากนั้นคุณสามารถใช้งานได้เช่น:

        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>();

เหตุใดคุณจึงสร้าง rng ใหม่ทุกครั้งที่เรียกใช้เมธอด ... คุณประกาศในระดับชั้น แต่ใช้เป็นภาษาท้องถิ่น ...
Yaron

1

การสุ่มอาร์เรย์มีความเข้มข้นเนื่องจากคุณต้องสลับไปมาเป็นจำนวนมาก ทำไมไม่อ่านแบบสุ่มจากอาเรย์ล่ะ? ในกรณีที่เลวร้ายที่สุดคุณสามารถสร้างคลาส wrapper ด้วย getNextString () หากคุณต้องการสร้างอาร์เรย์แบบสุ่มจริงๆคุณสามารถทำเช่นนั้นได้

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

* 5 นั้นเป็นกฎเกณฑ์


สุ่มอ่านจากอาเรย์มีแนวโน้มที่จะตีบางรายการหลายครั้งและคิดถึงคนอื่น ๆ !
เรย์เฮย์ส

อัลกอริทึมแบบสุ่มไม่ทำงาน คุณจะต้องทำให้ค่าอาร์ค 5 ของคุณสูงมากก่อนที่การสับเปลี่ยนจะไม่เอนเอียง
Pitarou

สร้าง Array ของดัชนี (จำนวนเต็ม) สุ่มดัชนี เพียงใช้ดัชนีตามลำดับแบบสุ่มนั้น ไม่มีการซ้ำซ้อนไม่มีการสับเปลี่ยนการอ้างอิงสตริงในหน่วยความจำ (ซึ่งแต่ละตัวเรียกทริกเกอร์การฝึกงานและสิ่งที่ไม่)
คริสโตเฟอร์

1

แค่คิดถึงส่วนบนของหัวฉันคุณก็ทำได้

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

สร้างอาร์เรย์ของการสุ่มลอยหรือ ints ที่มีความยาวเท่ากัน จัดเรียงอาร์เรย์นั้นและทำการสลับที่สอดคล้องกันบนอาร์เรย์เป้าหมายของคุณ

สิ่งนี้ให้ผลการเรียงลำดับที่เป็นอิสระอย่างแท้จริง


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

ตอนนี้ทางออกของคุณคือ IComparer ที่กำหนดเองไม่ปลอดภัย รูทีนการเรียงต้องการให้ตัวเปรียบเทียบเพื่อทำตามข้อกำหนดหลายประการเพื่อให้สามารถทำงานได้อย่างถูกต้อง ประการแรกในหมู่พวกเขาคือความมั่นคง หากมีการเปรียบเทียบเครื่องมือคู่เดียวกันมันจะต้องส่งคืนผลลัพธ์เดียวกันเสมอ (การเปรียบเทียบจะต้องเป็นสกรรมกริยา)

การไม่ปฏิบัติตามข้อกำหนดเหล่านี้อาจทำให้เกิดปัญหาจำนวนมากในรูทีนการเรียงรวมถึงความเป็นไปได้ของการวนซ้ำไม่สิ้นสุด

เกี่ยวกับวิธีแก้ปัญหาที่เชื่อมโยงค่าตัวเลขแบบสุ่มกับแต่ละรายการแล้วเรียงลำดับตามค่านั้นสิ่งเหล่านี้จะนำไปสู่อคติโดยธรรมชาติในผลลัพธ์เพราะเวลาใดก็ตามที่ทั้งสองรายการได้รับมอบหมายค่าตัวเลขเดียวกันการสุ่มของผลลัพธ์จะลดลง (ในรูทีนการจัดเรียง "เสถียร" แล้วแต่ว่าสิ่งใดเป็นอันดับแรกในอินพุตจะเป็นอันดับแรกในเอาต์พุต Array.Sort จะไม่เกิดความเสถียร แต่ยังมีอคติตามการแบ่งพาร์ติชันโดยอัลกอริทึม Quicksort)

คุณต้องคิดเกี่ยวกับระดับการสุ่มที่คุณต้องการ หากคุณใช้งานเว็บไซต์โป๊กเกอร์ที่คุณต้องการระดับการเข้ารหัสแบบสุ่มเพื่อป้องกันผู้โจมตีที่กำหนดคุณมีความต้องการที่แตกต่างกันมากจากคนที่เพียงต้องการสุ่มรายการเพลง

สำหรับการสับรายชื่อเพลงไม่มีปัญหาในการใช้ PRNG แบบ seeded (เช่น System.Random) สำหรับไซต์โปกเกอร์มันไม่ได้เป็นตัวเลือกและคุณต้องคิดเกี่ยวกับปัญหาที่หนักหนากว่าใคร ๆ จะทำเพื่อคุณในสแต็คโอเวอร์โฟลว์ (การใช้ RNG เข้ารหัสลับเป็นเพียงการเริ่มต้นเท่านั้นคุณต้องแน่ใจว่าอัลกอริทึมของคุณไม่ได้แนะนำอคติว่าคุณมีแหล่งข้อมูลเอนโทรปีเพียงพอและคุณไม่เปิดเผยสถานะภายในใด ๆ


0

โพสต์นี้ได้รับคำตอบค่อนข้างดี - ใช้การนำ Durstenfeld มาใช้ในการสับเปลี่ยน Fisher-Yates เพื่อผลลัพธ์ที่รวดเร็วและไม่เอนเอียง มีการใช้งานบางอย่างที่โพสต์ถึงแม้ว่าฉันทราบว่าบางอย่างไม่ถูกต้องจริง

ผมเขียนคู่ของโพสต์กลับในขณะที่เกี่ยวกับการดำเนินการฟืดเต็มรูปแบบและบางส่วนใช้เทคนิคนี้และ (ลิงค์ที่สองนี้เป็นที่ที่ฉันหวังที่จะสร้างมูลค่าเพิ่ม) นอกจากนี้ยังโพสต์ติดตามเกี่ยวกับวิธีการตรวจสอบว่าการดำเนินการของคุณเป็นที่เป็นกลาง , ซึ่งสามารถใช้เพื่อตรวจสอบอัลกอริทึมแบบสุ่มใด ๆ คุณสามารถดูในตอนท้ายของโพสต์ที่สองผลของความผิดพลาดง่าย ๆ ในการเลือกหมายเลขสุ่มสามารถทำได้


1
ลิงก์ของคุณยังขาดอยู่: /
Wai Ha Lee

0

ตกลงนี่เป็นชนที่ชัดเจนจากด้านข้างของฉัน (ขอโทษ ... ) แต่ฉันมักจะใช้วิธีการที่ค่อนข้างทั่วไปและ cryptographically

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 () เป็นส่วนขยายของ IEnumerable ใด ๆ ดังนั้นการรับพูดตัวเลขตั้งแต่ 0 ถึง 1,000 ตามลำดับแบบสุ่มในรายการสามารถทำได้ด้วย

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

วิธีการนี้จะไม่ทำให้ประหลาดใจเมื่อมาถึงการเรียงลำดับเนื่องจากค่าการเรียงถูกสร้างขึ้นและจดจำได้หนึ่งครั้งต่อองค์ประกอบในลำดับ


0

คุณไม่ต้องการอัลกอริทึมที่ซับซ้อน

บรรทัดง่าย ๆ เพียงบรรทัดเดียว:

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

โปรดทราบว่าเราจำเป็นต้องแปลงArrayเป็นรายการListแรกหากคุณไม่ได้ใช้Listตั้งแต่แรก

นอกจากนี้โปรดทราบว่านี่ไม่ได้มีประสิทธิภาพสำหรับอาร์เรย์ขนาดใหญ่มาก! ไม่อย่างนั้นมันจะสะอาดและเรียบง่าย


ข้อผิดพลาด: ผู้ประกอบการ '.' ไม่สามารถใช้กับตัวถูกดำเนินการประเภท 'void'
UsefulBee

0

นี่เป็นโซลูชันคอนโซลการทำงานที่สมบูรณ์ตามตัวอย่างที่ให้ไว้ที่นี่ :

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

นี่เป็นวิธีง่ายๆในการใช้ OLINQ:

// 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;
}

สำหรับฉันแล้วรู้สึกว่าคุณสามารถเพิ่มทั้งประสิทธิภาพและความพร้อมโดยแทนที่จะพยายามสลับ Array โดยการประกาศ Array อันที่สองคุณควรลองเปลี่ยนเป็น List, Shuffle และกลับไปเป็น Array:sortedList = source.ToList().OrderBy(x => generator.Next()).ToArray();
T_D
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.