คำตอบ:
มันไม่ใช่วิธีการสับที่ฉันชอบส่วนใหญ่เป็นเพราะ O (n log n) โดยไม่มีเหตุผลที่ดีเมื่อมันง่ายที่จะใช้การสลับ O (n) รหัสในคำถาม "ใช้งาน" โดยทั่วไปแล้วจะให้หมายเลข (หวังว่าจะไม่ซ้ำกัน!) โดยการสุ่มให้กับแต่ละองค์ประกอบจากนั้นทำการสั่งซื้อองค์ประกอบตามหมายเลขนั้น
ฉันชอบชุดสับเปลี่ยนแบบเปลี่ยนจากFisher-Yatesของ Durstenfield ซึ่งมีการสลับสับเปลี่ยนองค์ประกอบ
การใช้Shuffle
วิธีการขยายอย่างง่ายโดยทั่วไปจะประกอบด้วยการโทรToList
หรือToArray
ป้อนข้อมูลจากนั้นใช้การดำเนินการที่มีอยู่ของ Fisher-Yates (ผ่านRandom
เป็นพารามิเตอร์เพื่อให้ชีวิตโดยทั่วไปดีกว่า) มีการใช้งานมากมายรอบ ... ฉันอาจได้คำตอบในที่ใดที่หนึ่ง
สิ่งที่ดีเกี่ยวกับวิธีการขยายดังกล่าวก็คือมันจะมีความชัดเจนกับผู้อ่านว่าคุณกำลังพยายามทำอะไร
แก้ไข: นี่คือการใช้งานง่าย (ไม่มีการตรวจสอบข้อผิดพลาด!):
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random rng)
{
T[] elements = source.ToArray();
// Note i > 0 to avoid final pointless iteration
for (int i = elements.Length-1; i > 0; i--)
{
// Swap element "i" with a random earlier element it (or itself)
int swapIndex = rng.Next(i + 1);
T tmp = elements[i];
elements[i] = elements[swapIndex];
elements[swapIndex] = tmp;
}
// Lazily yield (avoiding aliasing issues etc)
foreach (T element in elements)
{
yield return element;
}
}
แก้ไข: ความคิดเห็นเกี่ยวกับประสิทธิภาพการทำงานด้านล่างเตือนฉันว่าเราสามารถส่งคืนองค์ประกอบจริง ๆ ได้ในขณะที่สับเปลี่ยนพวกเขา:
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random rng)
{
T[] elements = source.ToArray();
for (int i = elements.Length - 1; i >= 0; i--)
{
// Swap element "i" with a random earlier element it (or itself)
// ... except we don't really need to swap it fully, as we can
// return it immediately, and afterwards it's irrelevant.
int swapIndex = rng.Next(i + 1);
yield return elements[swapIndex];
elements[swapIndex] = elements[i];
}
}
ตอนนี้จะทำงานเท่าที่จำเป็น
โปรดทราบว่าในทั้งสองกรณีคุณต้องระมัดระวังเกี่ยวกับอินสแตนซ์ที่Random
คุณใช้เป็น:
Random
ที่ประมาณในเวลาเดียวกันจะให้ผลตามลำดับของตัวเลขสุ่ม (เมื่อใช้ในวิธีเดียวกัน)Random
ไม่ปลอดภัยสำหรับเธรดฉันมีบทความเกี่ยวกับRandom
รายละเอียดเพิ่มเติมเกี่ยวกับปัญหาเหล่านี้และการแก้ปัญหา
source.ToArray();
ว่าคุณต้องมีusing System.Linq;
ไฟล์เดียวกัน หากไม่เป็นเช่นนั้นคุณจะได้รับข้อผิดพลาดนี้:'System.Collections.Generic.IEnumerable<T>' does not contain a definition for 'ToArray' and no extension method 'ToArray' accepting a first argument of type 'System.Collections.Generic.IEnumerable<T>' could be found (are you missing a using directive or an assembly reference?)
นี้จะขึ้นอยู่กับจอนสกีตของคำตอบ
ในคำตอบนั้นอาร์เรย์จะถูกสับแล้วกลับมาใช้yield
ใหม่ ผลลัพธ์สุทธิคืออาร์เรย์ถูกเก็บไว้ในหน่วยความจำในช่วงระยะเวลาของการ foreach เช่นเดียวกับวัตถุที่จำเป็นสำหรับการทำซ้ำและยังมีค่าใช้จ่ายทั้งหมดที่จุดเริ่มต้น - ผลผลิตเป็นห่วงที่ว่างเปล่าโดยทั่วไป
อัลกอริทึมนี้มีการใช้มากในเกมที่มีการเลือกสามรายการแรกและอื่น ๆ จะต้องใช้ในภายหลังถ้าเลย ข้อเสนอแนะของฉันคือyield
ตัวเลขทันทีที่พวกเขาเปลี่ยน สิ่งนี้จะช่วยลดต้นทุนเริ่มต้นในขณะที่ยังคงต้นทุนการทำซ้ำที่ O (1) (โดยทั่วไปคือ 5 การดำเนินงานต่อการทำซ้ำ) ค่าใช้จ่ายทั้งหมดจะยังคงเท่าเดิม แต่การสับจะเร็วขึ้น ในกรณีที่สิ่งนี้ถูกเรียกว่าcollection.Shuffle().ToArray()
มันจะไม่สร้างความแตกต่างในทางทฤษฎี แต่ในกรณีการใช้งานดังกล่าวจะช่วยเร่งความเร็วในการสตาร์ทเครื่อง นอกจากนี้สิ่งนี้จะทำให้อัลกอริทึมมีประโยชน์สำหรับกรณีที่คุณต้องการเพียงรายการที่ไม่ซ้ำกัน ตัวอย่างเช่นหากคุณต้องการดึงไพ่สามใบออกจากสำรับไพ่ 52 ใบคุณสามารถโทรออกได้deck.Shuffle().Take(3)
และจะมีการแลกเปลี่ยนสามครั้งเท่านั้น (แม้ว่าจะต้องคัดลอกอาร์เรย์ทั้งหมดก่อน)
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random rng)
{
T[] elements = source.ToArray();
// Note i > 0 to avoid final pointless iteration
for (int i = elements.Length - 1; i > 0; i--)
{
// Swap element "i" with a random earlier element it (or itself)
int swapIndex = rng.Next(i + 1);
yield return elements[swapIndex];
elements[swapIndex] = elements[i];
// we don't actually perform the swap, we can forget about the
// swapped element because we already returned it.
}
// there is one item remaining that was not returned - we return it now
yield return elements[0];
}
เริ่มจากคำพูดของ Skeet นี้:
มันไม่ใช่วิธีการสับที่ฉันชอบส่วนใหญ่เป็นเพราะ O (n log n) โดยไม่มีเหตุผลที่ดีเมื่อมันง่ายที่จะใช้การสลับ O (n) รหัสในคำถาม "ใช้งาน" โดยทั่วไปแล้วจะให้หมายเลข ( หวังว่าจะไม่ซ้ำกัน! ) โดยการสุ่มให้กับแต่ละองค์ประกอบจากนั้นทำการสั่งซื้อองค์ประกอบตามหมายเลขนั้น
ฉันจะอธิบายเหตุผลเล็กน้อยเกี่ยวกับความหวังที่ไม่เหมือนใคร!
ตอนนี้จากEnumerable.OrderBy :
วิธีนี้ทำการเรียงลำดับที่เสถียร นั่นคือถ้าคีย์ของสององค์ประกอบเท่ากันลำดับขององค์ประกอบจะถูกเก็บรักษาไว้
สิ่งนี้สำคัญมาก! จะเกิดอะไรขึ้นถ้าสององค์ประกอบ "รับ" หมายเลขสุ่มเดียวกัน มันเกิดขึ้นที่พวกเขายังคงอยู่ในลำดับเดียวกันกับที่พวกเขาอยู่ในอาร์เรย์ ตอนนี้สิ่งที่เป็นไปได้ที่จะเกิดขึ้นคืออะไร? เป็นการยากที่จะคำนวณอย่างแน่นอน แต่มีปัญหาวันเกิดที่เป็นปัญหานี้อย่างแน่นอน
ตอนนี้จริงหรือ มันจริงหรอ?
และเช่นเคยหากมีข้อสงสัยให้เขียนโปรแกรมบางบรรทัด: http://pastebin.com/5CDnUxPG
บล็อกเล็ก ๆ ของโค้ดนี้จะสับเปลี่ยนอาร์เรย์ 3 องค์ประกอบจำนวนครั้งโดยใช้อัลกอริทึม Fisher-Yates ทำย้อนหลังอัลกอริทึม Fisher-Yates ทำไปข้างหน้า (ในหน้าwikiมีอัลกอริธึมหลอกรหัสสอง ... ผลลัพธ์ แต่หนึ่งเสร็จจากองค์ประกอบแรกถึงสุดท้ายขณะที่อีกองค์ประกอบหนึ่งทำจากองค์ประกอบสุดท้ายถึงองค์ประกอบแรก) อัลกอริทึมที่ไร้เดียงสาของhttp://blog.codinghorror.com/the-danger-of-naivete/และใช้.OrderBy(x => r.Next())
และ.OrderBy(x => r.Next(someValue))
และ
ตอนนี้Random.Nextคือ
จำนวนเต็มที่มีลายเซ็น 32- บิตที่มากกว่าหรือเท่ากับ 0 และน้อยกว่า MaxValue
ดังนั้นมันจึงเทียบเท่า
OrderBy(x => r.Next(int.MaxValue))
เพื่อทดสอบว่ามีปัญหานี้หรือไม่เราสามารถขยายอาร์เรย์ (บางสิ่งช้ามาก) หรือลดค่าสูงสุดของตัวสร้างหมายเลขสุ่ม ( int.MaxValue
ไม่ใช่หมายเลข "พิเศษ" ... มันเป็นเพียงตัวเลขที่ใหญ่มาก) ในท้ายที่สุดถ้าอัลกอริทึมไม่ได้มีความเอนเอียงโดยความนิ่งของOrderBy
ช่วงค่าใด ๆ ควรให้ผลลัพธ์เหมือนกัน
โปรแกรมจะทดสอบค่าบางค่าในช่วง 1 ... 4096 เมื่อดูที่ผลลัพธ์มันค่อนข้างชัดเจนว่าสำหรับค่าต่ำ (<128) อัลกอริทึมนั้นมีอคติมาก (4-8%) มี 3 r.Next(1024)
ค่าที่คุณต้องไม่น้อยกว่า หากคุณทำให้อาร์เรย์มีขนาดใหญ่ขึ้น (4 หรือ 5) แสดงว่ายังr.Next(1024)
ไม่เพียงพอ ฉันไม่ใช่ผู้เชี่ยวชาญในการสับและคณิตศาสตร์ แต่ฉันคิดว่าสำหรับความยาวพิเศษของแต่ละแถวคุณต้องมีค่าสูงสุดอีก 2 บิต (เนื่องจากวันเกิดเส้นขนานเชื่อมต่อกับ sqrt (ตัวเลข)) ดังนั้น ถ้าค่าสูงสุดคือ 2 ^ 31 ฉันจะบอกว่าคุณควรจะสามารถเรียงลำดับอาร์เรย์ได้มากถึง 2 ^ 12/2 ^ 13 บิต (องค์ประกอบ 4096-8192)
มันอาจเป็นไปได้อย่างสมเหตุสมผลสำหรับวัตถุประสงค์ส่วนใหญ่และเกือบทุกครั้งที่มันสร้างการแจกแจงแบบสุ่มอย่างแท้จริง (ยกเว้นเมื่อ Random.Next () สร้างจำนวนเต็มแบบสุ่มที่เหมือนกันสองรายการ)
มันทำงานได้โดยการกำหนดจำนวนเต็มขององค์ประกอบแต่ละชุดแบบสุ่มจากนั้นเรียงลำดับตามจำนวนเต็มเหล่านี้
เป็นที่ยอมรับโดยสิ้นเชิงสำหรับแอปพลิเคชัน 99.9% (เว้นแต่คุณจะต้องจัดการกับเคสขอบด้านบน) นอกจากนี้การคัดค้านของ skeet ไปยังรันไทม์ของมันนั้นถูกต้องดังนั้นหากคุณสับรายการยาว ๆ คุณอาจไม่ต้องการใช้มัน
เรื่องนี้เกิดขึ้นหลายครั้งก่อน ค้นหา Fisher-Yates บน StackOverflow
นี่คือตัวอย่างโค้ด C # ที่ฉันเขียนสำหรับอัลกอริทึมนี้ คุณสามารถกำหนดค่าพารามิเตอร์เป็นประเภทอื่นได้หากต้องการ
static public class FisherYates
{
// Based on Java code from wikipedia:
// http://en.wikipedia.org/wiki/Fisher-Yates_shuffle
static public void Shuffle(int[] deck)
{
Random r = new Random();
for (int n = deck.Length - 1; n > 0; --n)
{
int k = r.Next(n+1);
int temp = deck[n];
deck[n] = deck[k];
deck[k] = temp;
}
}
}
Random
เป็นตัวแปรคงที่เช่นนี้ - Random
ไม่ปลอดภัยสำหรับเธรด ดูcsharpindepth.com/Articles/Chapter12/Random.aspx
Random
จะเป็นความเจ็บปวดที่จะใช้ตามที่ระบุไว้ในบทความของฉัน
ดูเหมือนว่าอัลกอริทึมการสับที่ดีถ้าคุณไม่กังวลเกี่ยวกับประสิทธิภาพ ปัญหาเดียวที่ฉันชี้ให้เห็นก็คือพฤติกรรมของมันไม่สามารถควบคุมได้ดังนั้นคุณอาจทดสอบกับมันได้ยาก
ทางเลือกหนึ่งที่เป็นไปได้คือการให้ seed ส่งผ่านเป็นพารามิเตอร์ไปยังตัวสร้างหมายเลขสุ่ม (หรือตัวสร้างแบบสุ่มเป็นพารามิเตอร์) ดังนั้นคุณสามารถควบคุมได้มากขึ้นและทดสอบได้ง่ายขึ้น
ฉันพบว่าคำตอบของ Jon Skeet เป็นที่น่าพอใจอย่างสิ้นเชิง แต่ robo-scanner ของลูกค้าของฉันจะรายงานRandom
ว่ามีข้อบกพร่องด้านความปลอดภัย System.Security.Cryptography.RNGCryptoServiceProvider
ดังนั้นผมจึงสลับออกสำหรับ เป็นโบนัสมันแก้ไขปัญหาความปลอดภัยของเธรดที่กล่าวถึง ในทางกลับกันRNGCryptoServiceProvider
วัดได้ช้ากว่าการใช้ 300xRandom
ช้ากว่าการใช้
การใช้งาน:
using (var rng = new RNGCryptoServiceProvider())
{
var data = new byte[4];
yourCollection = yourCollection.Shuffle(rng, data);
}
วิธี:
/// <summary>
/// Shuffles the elements of a sequence randomly.
/// </summary>
/// <param name="source">A sequence of values to shuffle.</param>
/// <param name="rng">An instance of a random number generator.</param>
/// <param name="data">A placeholder to generate random bytes into.</param>
/// <returns>A sequence whose elements are shuffled randomly.</returns>
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, RNGCryptoServiceProvider rng, byte[] data)
{
var elements = source.ToArray();
for (int i = elements.Length - 1; i >= 0; i--)
{
rng.GetBytes(data);
var swapIndex = BitConverter.ToUInt32(data, 0) % (i + 1);
yield return elements[swapIndex];
elements[swapIndex] = elements[i];
}
}
กำลังมองหาอัลกอริทึมอยู่หรือ? คุณสามารถใช้ShuffleList
ชั้นเรียนของฉัน:
class ShuffleList<T> : List<T>
{
public void Shuffle()
{
Random random = new Random();
for (int count = Count; count > 0; count--)
{
int i = random.Next(count);
Add(this[i]);
RemoveAt(i);
}
}
}
จากนั้นใช้อย่างนี้:
ShuffleList<int> list = new ShuffleList<int>();
// Add elements to your list.
list.Shuffle();
ลองทำรายการเรียงลำดับเริ่มต้นของจำนวนเต็ม 5 ตัวแรก: { 0, 1, 2, 3, 4 }
ครั้งแรก:
count
วิธีการเริ่มต้นโดยการนับจำนวนเท่ากับจำนวนองค์ประกอบและเรียกมันว่า จากนั้นcount
เมื่อลดจำนวนลงในแต่ละขั้นตอนจะต้องใช้ตัวเลขสุ่มระหว่าง0
และcount
และย้ายไปยังจุดสิ้นสุดของรายการ
ในตัวอย่างทีละขั้นตอนต่อไปนี้รายการที่สามารถย้ายได้เป็นตัวเอียงรายการที่เลือกเป็นตัวหนา :
0 1 2 3 4
0 1 2 3 4
0 1 2 4 3
0 1 2 4 3
1 2 4 3 0
1 2 4 3 0
1 2 3 0 4
1 2 3 0 4
2 3 0 4 1
2 3 0 4 1
3 0 4 1 2
อัลกอริทึมนี้สลับโดยสร้างค่าสุ่มใหม่สำหรับแต่ละค่าในรายการจากนั้นเรียงลำดับรายการด้วยค่าสุ่มเหล่านั้น คิดว่ามันเป็นการเพิ่มคอลัมน์ใหม่ลงในตารางในหน่วยความจำแล้วเติมด้วย GUID แล้วเรียงลำดับตามคอลัมน์นั้น ดูเหมือนวิธีที่มีประสิทธิภาพสำหรับฉัน (โดยเฉพาะกับแลมบ์ดาน้ำตาล!)
ไม่เกี่ยวข้องกันเล็กน้อย แต่นี่เป็นวิธีที่น่าสนใจ (แม้ว่ามันจะมากเกินไป แต่ก็มีการนำไปใช้จริง ๆ ) สำหรับการเล่นลูกเต๋าแบบสุ่ม!
เหตุผลที่ฉันโพสต์สิ่งนี้ที่นี่คือเขาทำให้มีประเด็นที่น่าสนใจเกี่ยวกับวิธีที่ผู้ใช้โต้ตอบกับแนวคิดของการใช้อัลกอริทึมในการสับเปลี่ยนผ่านลูกเต๋าจริง แน่นอนในโลกแห่งความเป็นจริงการแก้ปัญหาดังกล่าวมีไว้สำหรับปลายสุดขั้วจริงๆเท่านั้นที่การสุ่มมีผลกระทบใหญ่และอาจส่งผลกระทบต่อเงิน;)
ฉันจะบอกว่าคำตอบมากมายที่นี่เช่น "อัลกอริทึมนี้จะสับเปลี่ยนโดยการสร้างค่าสุ่มใหม่สำหรับแต่ละค่าในรายการจากนั้นการเรียงลำดับรายการด้วยค่าสุ่มเหล่านั้น" อาจผิดพลาดมาก!
ฉันคิดว่านี่ไม่ได้กำหนดค่าการสุ่มให้กับแต่ละองค์ประกอบของคอลเลกชันแหล่งที่มา แต่อาจมีอัลกอริทึมการเรียงลำดับที่เรียกใช้เช่น Quicksort ซึ่งจะเรียกฟังก์ชันการเปรียบเทียบโดยประมาณประมาณ n บันทึก n ครั้ง เรียงลำดับบางอย่างคาดหวังจริง ๆ นี้ฟังก์ชั่นการเปรียบเทียบจะมีเสถียรภาพและมักจะส่งกลับผลลัพธ์เดียวกัน!
เป็นไปไม่ได้ที่ IEnumerableSorter จะเรียกใช้ฟังก์ชั่นการเปรียบเทียบสำหรับแต่ละขั้นตอนของอัลกอริทึมของเช่น quicksort และในแต่ละครั้งที่เรียกใช้ฟังก์ชันx => r.Next()
สำหรับพารามิเตอร์ทั้งสองโดยไม่ต้องแคชเหล่านี้!
ในกรณีนี้คุณอาจทำให้อัลกอริทึมการเรียงลำดับและทำให้แย่ลงกว่าความคาดหวังที่อัลกอริทึมถูกสร้างขึ้น แน่นอนว่าในที่สุดมันจะมีเสถียรภาพและคืนบางสิ่งบางอย่าง
ฉันอาจตรวจสอบในภายหลังโดยใส่การแก้ไขข้อบกพร่องในฟังก์ชัน "ถัดไป" ใหม่เพื่อดูว่าเกิดอะไรขึ้น ในตัวสะท้อนแสงฉันไม่สามารถรู้ได้ทันทีว่ามันทำงานอย่างไร
เวลาเริ่มต้นเพื่อรันโค้ดโดยล้างเธรดและแคชทั้งหมดสำหรับการทดสอบใหม่
รหัสแรกไม่สำเร็จ มันทำงานบน LINQPad หากคุณติดตามเพื่อทดสอบรหัสนี้
Stopwatch st = new Stopwatch();
st.Start();
var r = new Random();
List<string[]> list = new List<string[]>();
list.Add(new String[] {"1","X"});
list.Add(new String[] {"2","A"});
list.Add(new String[] {"3","B"});
list.Add(new String[] {"4","C"});
list.Add(new String[] {"5","D"});
list.Add(new String[] {"6","E"});
//list.OrderBy (l => r.Next()).Dump();
list.OrderBy (l => Guid.NewGuid()).Dump();
st.Stop();
Console.WriteLine(st.Elapsed.TotalMilliseconds);
list.OrderBy (x => r.Next ()) ใช้ 38.6528 ms
list.OrderBy (x => Guid.NewGuid ()) ใช้ 36.7634 ms (แนะนำจาก MSDN)
หลังจากครั้งที่สองทั้งสองใช้ในเวลาเดียวกัน
แก้ไข:
รหัสทดสอบบน Intel Core i7 4@2.1GHz, Ram 8 GB DDR3 @ 1600, HDD SATA 5200 รอบต่อนาทีพร้อม [ข้อมูล: www.dropbox.com/s/pbtmh5s9lw285kp/data]
using System;
using System.Runtime;
using System.Diagnostics;
using System.IO;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Threading;
namespace Algorithm
{
class Program
{
public static void Main(string[] args)
{
try {
int i = 0;
int limit = 10;
var result = GetTestRandomSort(limit);
foreach (var element in result) {
Console.WriteLine();
Console.WriteLine("time {0}: {1} ms", ++i, element);
}
} catch (Exception e) {
Console.WriteLine(e.Message);
} finally {
Console.Write("Press any key to continue . . . ");
Console.ReadKey(true);
}
}
public static IEnumerable<double> GetTestRandomSort(int limit)
{
for (int i = 0; i < 5; i++) {
string path = null, temp = null;
Stopwatch st = null;
StreamReader sr = null;
int? count = null;
List<string> list = null;
Random r = null;
GC.Collect();
GC.WaitForPendingFinalizers();
Thread.Sleep(5000);
st = Stopwatch.StartNew();
#region Import Input Data
path = Environment.CurrentDirectory + "\\data";
list = new List<string>();
sr = new StreamReader(path);
count = 0;
while (count < limit && (temp = sr.ReadLine()) != null) {
// Console.WriteLine(temp);
list.Add(temp);
count++;
}
sr.Close();
#endregion
// Console.WriteLine("--------------Random--------------");
// #region Sort by Random with OrderBy(random.Next())
// r = new Random();
// list = list.OrderBy(l => r.Next()).ToList();
// #endregion
// #region Sort by Random with OrderBy(Guid)
// list = list.OrderBy(l => Guid.NewGuid()).ToList();
// #endregion
// #region Sort by Random with Parallel and OrderBy(random.Next())
// r = new Random();
// list = list.AsParallel().OrderBy(l => r.Next()).ToList();
// #endregion
// #region Sort by Random with Parallel OrderBy(Guid)
// list = list.AsParallel().OrderBy(l => Guid.NewGuid()).ToList();
// #endregion
// #region Sort by Random with User-Defined Shuffle Method
// r = new Random();
// list = list.Shuffle(r).ToList();
// #endregion
// #region Sort by Random with Parallel User-Defined Shuffle Method
// r = new Random();
// list = list.AsParallel().Shuffle(r).ToList();
// #endregion
// Result
//
st.Stop();
yield return st.Elapsed.TotalMilliseconds;
foreach (var element in list) {
Console.WriteLine(element);
}
}
}
}
}
คำอธิบายผลลัพธ์: https://www.dropbox.com/s/9dw9wl259dfs04g/ResultDescription.PNG
สถิติผลลัพธ์: https://www.dropbox.com/s/ewq5ybtsvesme4d/ResultStat.PNG
สรุป:
สมมติว่า: LINQ OrderBy (r.Next ()) และ OrderBy (Guid.NewGuid ()) นั้นไม่ได้แย่ไปกว่าวิธีการสลับแบบกำหนดเองของผู้ใช้ในโซลูชันแรก
คำตอบ: พวกเขาขัดแย้งกัน