สุ่มรายการ <T>


852

วิธีที่ดีที่สุดในการสุ่มลำดับของรายการทั่วไปใน C # คืออะไร ฉันมีหมายเลข 75 ชุดที่แน่นอนในรายการที่ฉันต้องการกำหนดลำดับแบบสุ่มเพื่อวาดพวกเขาสำหรับแอปพลิเคชันประเภทลอตเตอรี


3
มีปัญหาแบบเปิดเพื่อรวมฟังก์ชันการทำงานนี้กับ. NET: github.com/dotnet/corefx/issues/461
Natan

5
คุณอาจสนใจแพคเกจ NuGet นี้ซึ่งมีวิธีการขยายสำหรับการสับ IList <T> และ IEnumerable <T> โดยใช้อัลกอริทึม Fisher-Yates ที่กล่าวถึงด้านล่าง
ChaseMedallion

3
@Natan พวกเขาปิดปัญหาเพราะมีคน "ทำงานในหลายโครงการและพัฒนาห้องสมุดจำนวนมากและไม่เคยมีความต้องการในวิธีการ" ที่ทำให้ฉันโกรธ ตอนนี้เราต้องตรวจสอบตัวเองค้นหาการใช้งานที่ดีที่สุดเสียเวลาเพียงแค่คิดค้นล้อใหม่
Vitalii Isaenko

1
ฉันเห็นสิ่งนี้ถูกไหม? ไม่ใช่คำตอบที่ใช้งานได้ถูกต้องหลังจาก 10 ปีใช่ไหม บางทีเราอาจต้องการค่าตอบแทนเพิ่มอีกหนึ่งค่าสำหรับวิธีแก้ปัญหาซึ่งระบุจำนวนของเอนโทรปีที่ต้องการเพื่อสับเปลี่ยนรายการด้วย 75 หมายเลข $ log2 (75!) = 364 $ และวิธีที่เราจะได้รับสิ่งนี้ เราจะต้องทำการ reseed แม้แต่ RNG ที่ปลอดภัยด้วยการเข้ารหัสด้วย 256 บิตของเอนโทรปีอย่างน้อยหนึ่งครั้งในระหว่างการสับเปลี่ยนชาวประมงเยตส์
Falco

1
และถ้า coder ปกติไม่สามารถแก้ปัญหานี้ได้พวกเราทุกคนเล่นเกมโซลิแทร์ที่เป็นไปได้ 0.01% ตลอดไปหรือไม่?
Falco

คำตอบ:


1135

สุ่มใด ๆ(I)Listด้วยวิธีการขยายตามแบบสุ่มของFisher-Yates :

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

การใช้งาน:

List<Product> products = GetProducts();
products.Shuffle();

รหัสด้านบนใช้วิธีการ System.Random ที่ถูกวิพากษ์วิจารณ์มากเพื่อเลือกตัวเลือกการสลับ มันเร็ว แต่ไม่สุ่มเท่าที่ควร หากคุณต้องการคุณภาพแบบสุ่มใน shuffles ที่ดีขึ้นให้ใช้ตัวสร้างตัวเลขสุ่มใน System.Security.Cryptography ดังนี้:

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

มีการเปรียบเทียบแบบง่าย ๆที่บล็อกนี้ (WayBack Machine)

แก้ไข: ตั้งแต่เขียนคำตอบนี้สองสามปีที่ผ่านมาหลายคนแสดงความคิดเห็นหรือเขียนถึงฉันเพื่อชี้ให้เห็นข้อบกพร่องที่โง่ใหญ่ในการเปรียบเทียบของฉัน แน่นอนพวกเขาถูกต้อง ไม่มีอะไรผิดปกติกับ System.Random ถ้ามันถูกใช้ในแบบที่มันตั้งใจไว้ ในตัวอย่างแรกของฉันด้านบนฉันสร้างอินสแตนซ์ของตัวแปร rng ภายในวิธี Shuffle ซึ่งจะถามถึงปัญหาว่าจะเรียกวิธีนั้นซ้ำ ๆ หรือไม่ ด้านล่างนี้เป็นตัวอย่างแบบเต็มที่ยึดตามความคิดเห็นที่เป็นประโยชน์จริง ๆ ที่ได้รับในวันนี้จาก @weston ที่นี่ใน SO

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

31
ถ้า list.Count คือ> Byte.MaxValue ถ้า n = 1,000 ดังนั้น 255/1000 = 0 ดังนั้นลูป do จะเป็นลูปไม่สิ้นสุดเนื่องจากกล่อง [0] <0 เป็นเท็จเสมอ
AndrewS

18
ฉันต้องการจะชี้ให้เห็นว่าการเปรียบเทียบมีข้อบกพร่อง การใช้ <code> ใหม่สุ่ม () </code> ในลูปเป็นปัญหาไม่ใช่การสุ่มของ <code> สุ่ม </code> คำอธิบาย
สเวน

9
มันเป็นความคิดที่ดีที่จะส่งตัวอย่างของการสุ่มไปยังวิธีการสุ่มแทนที่จะสร้างมันภายในราวกับว่าคุณกำลังเรียก Shuffle หลายครั้งอย่างต่อเนื่องอย่างรวดเร็ว (เช่นการสับรายการสั้น ๆ จำนวนมาก) รายการทั้งหมดจะถูกสับเปลี่ยนในแบบเดียวกัน ทาง (เช่นรายการแรกจะถูกย้ายไปที่ตำแหน่ง 3 เสมอ)
Mark Heath

7
เพียงแค่การทำจะแก้ปัญหาในการโพสต์การเปรียบเทียบ เนื่องจากแต่ละการโทรที่ตามมาจะตามมาจากการโทรก่อนหน้านี้ผลการสุ่มครั้งสุดท้าย Random rng = new Random();static
weston

5
# 2 มันไม่ชัดเจนว่ารุ่นที่มีตัวสร้าง Crypto ทำงานได้เนื่องจากช่วงสูงสุดของไบต์คือ 255 ดังนั้นรายการใดที่มีขนาดใหญ่กว่านั้นจะไม่สามารถสลับได้อย่างถูกต้อง
Mark Sowul

336

หากเราต้องการสลับรายการในลำดับแบบสุ่มอย่างสมบูรณ์ (เพียงเพื่อผสมรายการในรายการ) ฉันต้องการรหัสที่เรียบง่ายและมีประสิทธิภาพซึ่งสั่งซื้อรายการตามแนวทาง ...

var shuffledcards = cards.OrderBy(a => Guid.NewGuid()).ToList();

40
GUID นั้นไม่ซ้ำแบบใคร ส่วนหนึ่งเป็นเครื่องที่ใช้และอีกส่วนตามเวลาและมีเพียงส่วนเล็ก ๆ เท่านั้นที่เป็นแบบสุ่ม blogs.msdn.com/b/oldnewthing/archive/2008/06/27/8659071.aspx
Despertar

99
นี่เป็นทางออกที่ดีงาม หากคุณต้องการสิ่งอื่นที่ไม่ใช่ guid เพื่อสร้างการสุ่มให้เรียงลำดับอย่างอื่น เช่น: var shuffledcards = cards.OrderBy(a => rng.Next()); compilr.com/grenade/sandbox/Program.cs
grenade

20
กรุณาอย่า นี่เป็นสิ่งที่ผิด "การสั่งซื้อโดยการสุ่ม" ไม่ใช่การสลับกันโดยสิ้นเชิง: คุณแนะนำอคติและยิ่งแย่กว่านั้นคุณเสี่ยงที่จะเข้าสู่วงวนไม่สิ้นสุด
Vito De Tullio

78
@VitoDeTullio: คุณกำลังเข้าใจผิด คุณมีความเสี่ยงลูปไม่มีที่สิ้นสุดเมื่อคุณให้สุ่มฟังก์ชั่นการเปรียบเทียบ ; ฟังก์ชั่นการเปรียบเทียบเป็นสิ่งจำเป็นในการผลิตที่สอดคล้องกันเพื่อรวม คีย์สุ่มนั้นใช้ได้ คำแนะนำนี้ผิดเพราะguids ไม่รับประกันว่าจะสุ่มไม่ใช่เพราะเทคนิคการเรียงลำดับโดยการสุ่มคีย์ผิด
Eric Lippert

24
@Doug: NewGuidเพียงรับประกันว่าจะให้ GUID ที่ไม่ซ้ำกัน มันไม่รับประกันเกี่ยวกับการสุ่ม หากคุณใช้ GUID เพื่อจุดประสงค์อื่นนอกเหนือจากการสร้างคุณค่าที่ไม่เหมือนใครแสดงว่าคุณทำผิด
Eric Lippert

120

ฉันประหลาดใจนิดหน่อยกับอัลกอริธึมแบบง่าย ๆ ที่นี่ Fisher-Yates (หรือ Knuth shuffle) ค่อนข้างยุ่งยาก แต่ก็เล็กมาก ทำไมมันยุ่งยาก? เพราะคุณต้องให้ความสนใจกับตัวสร้างตัวเลขสุ่มของคุณr(a,b)คืนค่าที่bรวมหรือไม่รวม ฉันได้แก้ไขคำอธิบายของ Wikipedia ด้วยดังนั้นผู้คนจึงไม่ติดตาม pseudocode ที่นั่นและสร้างยากที่จะตรวจจับข้อบกพร่อง สำหรับ. Net Random.Next(a,b)จะคืนค่าตัวเลขbให้โดยไม่ต้องกังวลใจต่อไปนี่คือวิธีการใช้ใน C # /. Net:

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

ลองรหัสนี้


จะดีกว่าไหมถ้าเปลี่ยน rnd (i, list.Count) เป็น rnd (0, list.Count) เพื่อให้การ์ดใด ๆ ถูกเปลี่ยน?
โดนัท

16
@ Donuts - ไม่ หากคุณทำเช่นนั้นคุณจะเพิ่มอคติในแบบสุ่ม
Shital Shah

2
โดยการแยก Swap <T> ออกเป็นวิธีแยกดูเหมือนว่าคุณจะทำให้การจัดสรร T ที่ไม่จำเป็นสำหรับอุณหภูมิ
Clay

2
ฉันขอยืนยันว่า LINQ อาจทำให้ประสิทธิภาพการสับเปลี่ยนช้าลงและนั่นอาจเป็นเหตุผลที่จะไม่ใช้มันโดยเฉพาะอย่างยิ่งเมื่อได้รับความเรียบง่ายสัมพัทธ์ของรหัส
winglerw28

7
เมื่อi = list.Count - 1เช่นการทำซ้ำครั้งล่าสุดrnd.Next(i, list.Count)จะทำให้คุณกลับมา คุณจำเป็นต้องใช้i < list.Count -1ตามเงื่อนไขลูป คุณไม่จำเป็นต้องใช้มัน แต่มันช่วยประหยัด 1 ซ้ำ;)
Pod

78

วิธีการขยายสำหรับ IEnumerable:

public static IEnumerable<T> Randomize<T>(this IEnumerable<T> source)
{
    Random rnd = new Random();
    return source.OrderBy<T, int>((item) => rnd.Next());
}

3
โปรดทราบว่านี่ไม่ปลอดภัยสำหรับเธรดแม้ว่าจะใช้กับรายการที่ปลอดภัยสำหรับเธรด
BlueRaja - Danny Pflughoeft

1
เราจะให้ list <string> กับฟังก์ชันนี้ได้อย่างไร
MonsterMMORPG

8
มีปัญหาที่สำคัญสองประการเกี่ยวกับอัลกอริทึมนี้: - OrderByใช้ตัวแปร QuickSort เพื่อเรียงลำดับรายการด้วยปุ่ม (สุ่มอย่างชัดเจน) ประสิทธิภาพ QuickSort คือO (N log N) ; ในทางตรงกันข้ามการสับเปลี่ยน Fisher-Yates เป็นO (N) สำหรับชุด 75 องค์ประกอบนี่อาจไม่ใช่เรื่องใหญ่ แต่ความแตกต่างจะเด่นชัดสำหรับคอลเลกชันขนาดใหญ่
John Beyer

10
... - Random.Next()อาจสร้างการแจกแจงแบบหลอกเทียมอย่างสมเหตุสมผล แต่ไม่รับประกันว่าค่าจะไม่ซ้ำกัน ความน่าจะเป็นของคีย์ซ้ำจะเพิ่มขึ้น (ไม่ใช่แบบเส้นตรง) ด้วยNจนกว่าจะถึงความมั่นใจเมื่อNถึง 2 ^ 32 + 1 OrderByQuickSort เป็นมีเสถียรภาพการจัดเรียง; ดังนั้นหากหลายองค์ประกอบเกิดขึ้นเพื่อให้ได้รับค่าดัชนีหลอกแบบสุ่มเดียวกันดังนั้นลำดับของพวกเขาในลำดับเอาต์พุตจะเหมือนกับในลำดับอินพุต; ดังนั้นความเอนเอียงจะถูกนำเข้าสู่ "การสุ่ม"
John Beyer

27
@JohnBeyer: มีปัญหาไกลและยิ่งใหญ่กว่าแหล่งที่มีอคติ มีเพียงสี่พันล้านเมล็ดที่เป็นไปได้สำหรับการสุ่มซึ่งอยู่ไกลน้อยกว่าจำนวนสับที่เป็นไปได้ของชุดที่มีขนาดปานกลาง สามารถสร้างได้เพียงส่วนเล็ก ๆ ของ shuffles อคตินั้นทำให้คนแคระอคติเนื่องจากการชนกันโดยบังเอิญ
Eric Lippert

15

แนวคิดคือการได้รับวัตถุที่ไม่ระบุชื่อพร้อมรายการและคำสั่งแบบสุ่มจากนั้นเรียงลำดับรายการตามคำสั่งซื้อนี้และค่าส่งคืน:

var result = items.Select(x => new { value = x, order = rnd.Next() })
            .OrderBy(x => x.order).Select(x => x.value).ToList()

3
วิธีแก้ปัญหาซับที่ดีที่สุดหนึ่งเดียว
vipin8169

1
คุณพลาดเซมิโคลอนในตอนท้าย fyi
reggaeguitar

หากใครไม่แน่ใจเกี่ยวกับ rnd เพิ่มนี้ก่อนที่จะรหัสข้างต้นสุ่ม rnd = ใหม่สุ่ม ();
Greg Trevellick

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


4
คุณไม่ควรทำสิ่งที่ชอบvar listCopy = list.ToList()หลีกเลี่ยงการดึงรายการทั้งหมดออกจากรายการที่เข้ามาหรือไม่? ฉันไม่เห็นว่าทำไมคุณต้องการกลายพันธุ์รายการเหล่านั้นให้ว่างเปล่า
Chris Marisic

9

แก้ไขRemoveAtเป็นจุดอ่อนในรุ่นก่อนหน้าของฉัน การแก้ปัญหานี้เอาชนะว่า

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

หมายเหตุทางเลือกRandom generatorถ้าการใช้งานเฟรมเวิร์กพื้นฐานของRandomthread-safe หรือ cryptographically นั้นไม่เพียงพอสำหรับความต้องการของคุณคุณสามารถแทรกการใช้งานของคุณไปยังการดำเนินการ

การใช้งานที่เหมาะสมสำหรับการRandomใช้งานที่แข็งแกร่งของเธรดเข้ารหัสปลอดภัยสามารถพบได้ในคำตอบนี้


นี่คือแนวคิดขยาย IList ด้วยวิธีที่มีประสิทธิภาพ (หวังว่า)

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


ดูstackoverflow.com/questions/4412405/... คุณต้องระวังอยู่แล้ว
nawfal

@nawfal เห็นการปรับปรุงการใช้งานของฉัน
Jodrell

1
อืมยุติธรรมพอ มันเป็นGetNextหรือNext?
nawfal

4

คุณสามารถทำได้โดยใช้วิธีการขยายอย่างง่ายนี้

public static class IEnumerableExtensions
{

    public static IEnumerable<t> Randomize<t>(this IEnumerable<t> target)
    {
        Random r = new Random();

        return target.OrderBy(x=>(r.Next()));
    }        
}

และคุณสามารถใช้งานได้โดยทำดังต่อไปนี้

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

3
ฉันจะเก็บRandomอินสแตนซ์ของคลาสนอกฟังก์ชันเป็นstaticตัวแปร มิฉะนั้นคุณอาจได้รับเมล็ดการสุ่มเดียวกันจากตัวจับเวลาหากเรียกอย่างรวดเร็ว
Lemonseed

หมายเหตุที่น่าสนใจ - หากคุณยกตัวอย่างคลาส Random อย่างรวดเร็วภายในลูปให้พูดระหว่าง 0 ms ถึง 200 ms ของแต่ละคนดังนั้นคุณมีโอกาสสูงมากที่จะได้รับเมล็ดการสุ่มเดียวกันซึ่งจะทำให้ได้ผลลัพธ์ซ้ำ อย่างไรก็ตามคุณสามารถหลีกเลี่ยงปัญหานี้ได้ด้วยการใช้แรนด์แบบสุ่ม = สุ่มแบบใหม่ (Guid.NewGuid () GetHashCode ()); สิ่งนี้บังคับให้การสุ่มอย่างมีประสิทธิภาพนั้นมาจาก Guid.NewGuid ()
Baaleos

4

นี่เป็นวิธีการสลับแบบที่ฉันต้องการเมื่อไม่ต้องการแก้ไขต้นฉบับ มันเป็นตัวแปรของอัลกอริทึม Fisher-Yates "Inside-out"ที่ทำงานบนลำดับที่นับได้ (ความยาวsourceไม่จำเป็นต้องทราบตั้งแต่เริ่มต้น)

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

อัลกอริทึมนี้ยังสามารถนำมาใช้โดยการจัดสรรช่วงจาก0ถึงlength - 1และสุ่มดัชนีโดยการแลกเปลี่ยนดัชนีที่เลือกแบบสุ่มกับดัชนีสุดท้ายจนกว่าดัชนีทั้งหมดจะถูกเลือกเพียงครั้งเดียว รหัสข้างต้นนี้บรรลุสิ่งเดียวกัน แต่ไม่มีการจัดสรรเพิ่มเติม ซึ่งค่อนข้างเรียบร้อย

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

var bytes = new byte[8];
_secureRng.GetBytes(bytes);
var v = BitConverter.ToUInt64(bytes, 0);
return (double)v / ((double)ulong.MaxValue + 1);

จุดของการสร้างคู่แบบสุ่ม (ระหว่าง 0 ถึง 1 เท่านั้น) คือการใช้ในการปรับขนาดเพื่อแก้ปัญหาจำนวนเต็ม หากคุณต้องการเลือกบางสิ่งจากรายการโดยพิจารณาจากการสุ่มสองเท่าxนั่นจะเป็น0 <= x && x < 1สิ่งที่ตรงไปตรงมาเสมอ

return list[(int)(x * list.Count)];

สนุก!


4

หากคุณไม่คำนึงถึงการใช้สองListsสิ่งนี้อาจเป็นวิธีที่ง่ายที่สุดที่จะทำ แต่อาจไม่ใช่วิธีที่มีประสิทธิภาพที่สุดหรือคาดเดาไม่ได้:

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

3

หากคุณมีหมายเลขคงที่ (75) คุณสามารถสร้างอาร์เรย์ที่มีองค์ประกอบ 75 รายการจากนั้นระบุรายการของคุณย้ายองค์ประกอบไปยังตำแหน่งที่สุ่มในอาร์เรย์ คุณสามารถสร้างการทำแผนที่ของจำนวนรายการเพื่อดัชนีอาร์เรย์ใช้สับเปลี่ยน Fisher-Yates


3

ฉันมักจะใช้:

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.RemoveAt เป็นการดำเนินการ O (n) ซึ่งทำให้การใช้งานช้าลงอย่างไม่ได้ผล
George Polevoy

1
    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.

0

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

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

`


0

นี่เป็นวิธีที่ปลอดภัยต่อการทำเช่นนี้:

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

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

}

2
ยินดีต้อนรับสู่ Stack Overflow! โปรดพิจารณาการเพิ่มคำอธิบายลงในคำตอบของคุณแทนที่จะเป็นเพียงแค่บล็อกโค้ดขนาดใหญ่ เป้าหมายของเราที่นี่คือการให้การศึกษาแก่ผู้คนเพื่อให้พวกเขาเข้าใจคำตอบและสามารถนำไปใช้ในสถานการณ์อื่น ๆ ได้ หากคุณแสดงความคิดเห็นรหัสของคุณและเพิ่มคำอธิบายคุณจะทำให้คำตอบของคุณมีประโยชน์มากขึ้นไม่ใช่เฉพาะกับคนที่ถามคำถามในครั้งนี้ แต่สำหรับทุกคนในอนาคตที่อาจมีปัญหาเดียวกัน
starsplusplus

4
ส่วนใหญ่ของรหัสนี้ไม่เกี่ยวข้องกับคำถามทั้งหมดและส่วนที่มีประโยชน์เพียงอย่างเดียวโดยทั่วไปแล้วซ้ำคำตอบของ Adam Tegen เมื่อเกือบ 6 ปีที่แล้ว
TC

0

การแก้ไขคำตอบที่ยอมรับได้ง่ายซึ่งส่งคืนรายการใหม่แทนที่จะทำงานในสถานที่และยอมรับIEnumerable<T>วิธีการอื่น ๆ ของ Linq

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


-5

โพสต์เก่าแน่นอน แต่ฉันเพิ่งใช้ GUID

Items = Items.OrderBy(o => Guid.NewGuid().ToString()).ToList();

GUID นั้นมีความเป็นเอกลักษณ์อยู่เสมอและจะถูกสร้างใหม่ทุกครั้งที่มีการเปลี่ยนแปลงผลลัพธ์ในแต่ละครั้ง


กะทัดรัด แต่คุณมีข้อมูลอ้างอิงเกี่ยวกับการเรียงลำดับของ newGuids ที่ต่อเนื่องกันเป็นแบบสุ่มคุณภาพสูงหรือไม่ บางรุ่นของ quid / uuid มีการประทับเวลาและชิ้นส่วนที่ไม่ใช่แบบสุ่มอื่น ๆ
Johan Lundberg

8
คำตอบนี้ได้รับแล้วและยิ่งแย่ไปกว่านั้นคือมันถูกออกแบบมาเพื่อความเป็นเอกลักษณ์ไม่ใช่แบบแผน
Alex Angas

-7

วิธีการที่ง่ายมากสำหรับปัญหาประเภทนี้คือการใช้การสลับองค์ประกอบแบบสุ่มจำนวนหนึ่งในรายการ

ในรหัสหลอกนี้จะมีลักษณะเช่นนี้:

do 
    r1 = randomPositionInList()
    r2 = randomPositionInList()
    swap elements at index r1 and index r2 
for a certain number of times

1
ปัญหาอย่างหนึ่งของวิธีนี้คือการรู้ว่าเมื่อใดจะหยุด นอกจากนี้ยังมีแนวโน้มที่จะพูดเกินจริงอคติใด ๆ ในเครื่องกำเนิดตัวเลขแบบหลอก
Mark Bessey

3
ใช่. ไม่มีประสิทธิภาพสูง ไม่มีเหตุผลที่จะใช้วิธีการเช่นนี้เมื่อวิธีการที่ดีกว่าและเร็วกว่านั้นมีอยู่ที่เรียบง่าย
PeterAllenWebb

1
ไม่มีประสิทธิภาพหรือมีประสิทธิภาพมาก ... การรันโดยใช้ N ครั้งอาจทำให้องค์ประกอบหลายอย่างอยู่ในตำแหน่งเดิม
NSjonas
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.