C #에서 일반 목록의 순서를 무작위로 만드는 가장 좋은 방법은 무엇입니까? 복권 유형 응용 프로그램을 위해 무작위 순서를 할당하고 싶은 목록에 유한 한 75 개의 숫자 세트가 있습니다.
답변
어떤 셔플 (I)List
에 기초 확장 방법 피셔 – 예이츠 셔플 :
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 메서드를 사용하여 스왑 후보를 선택합니다. 빠르지 만 무작위는 아닙니다. 셔플에서 더 나은 품질의 무작위성이 필요한 경우 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을 사용하는 경우 아무런 문제가 없습니다. 위의 첫 번째 예에서는 Shuffle 메서드 내에서 rng 변수를 인스턴스화하여 메서드를 반복적으로 호출해야하는지 문제를 묻습니다. 아래는 @weston이 오늘 여기에서받은 정말 유용한 의견을 바탕으로 한 고정 된 전체 예입니다.
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;
}
}
}
}
답변
우리가 완전히 무작위 순서로 항목을 섞어 야 할 경우 (목록에 항목을 혼합하기 위해), guid별로 항목을 주문하는이 간단하지만 효과적인 코드를 선호합니다 …
var shuffledcards = cards.OrderBy(a => Guid.NewGuid()).ToList();
답변
이 간단한 알고리즘의 모든 버전이 놀랍습니다. Fisher-Yates (또는 Knuth shuffle)는 약간 까다 롭지 만 매우 컴팩트합니다. 왜 까다로운가요? 난수 생성기 가 포함 또는 배타적 인 r(a,b)
값을 반환 하는지 여부에주의를 기울여야하기 때문에 b
. 또한 Wikipedia 설명 을 편집 하여 사람들이 의사 코드를 맹목적으로 따르지 않고 버그를 감지하기 어렵게 만들었습니다. .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;
}
답변
IEnumerable의 확장 방법 :
public static IEnumerable<T> Randomize<T>(this IEnumerable<T> source)
{
Random rnd = new Random();
return source.OrderBy<T, int>((item) => rnd.Next());
}
답변
아이디어는 항목과 임의 순서로 익명 객체를 얻은 다음이 순서로 항목을 다시 정렬하고 값을 반환합니다.
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;
}
답변
편집
은 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
. 기본 프레임 워크 구현이 Random
스레드에 안전하지 않거나 필요에 따라 암호화 방식으로 강력하지 않은 경우 구현에 작업을 삽입 할 수 있습니다.
스레드 안전 암호화 방식으로 강력한 Random
구현에 적합한 구현 이이 답변에서 찾을 수 있습니다.
아이디어가 있습니다. (Ihope) 효율적인 방법으로 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]];
}
