C #에서 일반 목록의 순서를 무작위로 만드는 가장 좋은 방법은 무엇입니까? 복권 유형 응용 프로그램을 위해 무작위 순서를 할당하고 싶은 목록에 유한 한 75 개의 숫자 세트가 있습니다.
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;
}
}
}
}
Random rng = new Random();
A는 static
비교 게시물에 문제를 해결할 것입니다. 이후의 각 호출은 이전 호출에서 마지막 임의의 결과로 이어집니다.
우리가 완전히 무작위 순서로 항목을 섞어 야 할 경우 (목록에 항목을 혼합하기 위해), guid별로 항목을 주문하는이 간단하지만 효과적인 코드를 선호합니다 ...
var shuffledcards = cards.OrderBy(a => Guid.NewGuid()).ToList();
var shuffledcards = cards.OrderBy(a => rng.Next());
compilr.com/grenade/sandbox/Program.cs
NewGuid
유일한 GUID를 보장합니다. 무작위성을 보장하지 않습니다. 고유 한 값을 만드는 것 이외의 목적으로 GUID를 사용 하는 경우 잘못된 값을 사용하고 있습니다.
이 간단한 알고리즘의 모든 버전이 놀랍습니다. 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;
}
i = list.Count - 1
, 즉 마지막 반복 rnd.Next(i, list.Count)
은 내가 당신을 돌려 줄 것입니다. 따라서 i < list.Count -1
루프 조건으로 필요 합니다. 글쎄, 당신은 그것을 필요로하지 않지만 1 반복을 절약합니다.)
IEnumerable의 확장 방법 :
public static IEnumerable<T> Randomize<T>(this IEnumerable<T> source)
{
Random rnd = new Random();
return source.OrderBy<T, int>((item) => rnd.Next());
}
OrderBy
QuickSort 변형을 사용하여 항목을 임의적으로 (가시적으로 임의의) 키로 정렬합니다. QuickSort 성능은 O (N log N)입니다 . 반대로 Fisher-Yates 셔플은 O (N) 입니다. 75 개 요소의 컬렉션에 대해서는 큰 문제가되지 않지만 더 큰 컬렉션에 대해서는 그 차이가 뚜렷해집니다.
Random.Next()
합리적인 의사 랜덤 분포 값을 생성 할 수 있지만 값이 고유하다는 보장 은 없습니다 . 중복 키의 확률은 N 이 2 ^ 32 + 1에 도달 할 때 확실성에 도달 할 때까지 N 과 비선형으로 증가 합니다. 이것은 QuickSort은이다 안정의 종류; 따라서 여러 요소에 동일한 의사 난수 인덱스 값이 할당되면 출력 순서의 순서는 입력 순서 와 동일 합니다. 따라서, "셔플"에 바이어스가 도입된다. OrderBy
아이디어는 항목과 임의 순서로 익명 객체를 얻은 다음이 순서로 항목을 다시 정렬하고 값을 반환합니다.
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;
}
var listCopy = list.ToList()
들어오는 목록에서 모든 항목이 튀어 나오는 것을 피하고 싶지 않은가? 나는 왜 당신이 그 목록을 비우고 싶은지 알지 못합니다.
편집
은 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]];
}
GetNext
또는 Next
?
이 간단한 확장 방법을 사용하여 달성 할 수 있습니다
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);
}
Random
클래스 인스턴스를 함수 외부의 static
변수 로 유지합니다 . 그렇지 않으면 빠른 연속으로 호출되면 타이머에서 동일한 무작위 시드를 얻을 수 있습니다.
이것은 원본을 수정하지 않는 것이 좋을 때 선호하는 셔플 방법입니다. 열거 가능한 시퀀스에서 작동 하는 Fisher–Yates "내부"알고리즘 의 변형입니다 (길이 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)];
즐겨!
고정 번호 (75)가있는 경우 75 개의 요소가있는 배열을 만든 다음 요소를 배열의 무작위 위치로 이동하여 목록을 열거 할 수 있습니다. Fisher-Yates shuffle을 사용하여 목록 번호와 배열 인덱스의 매핑을 생성 할 수 있습니다 .
나는 보통 다음을 사용합니다.
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<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.
다음은 섞인 값의 바이트 배열을 반환하는 효율적인 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;
}
`
이를 수행하는 스레드 안전 방법은 다음과 같습니다.
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());
}
}
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);
}
제자리에서 작업하는 대신 새 목록을 반환하고 다른 많은 Linq 메서드가 수행 하는 것보다 더 일반적인 것을 허용 하는 수락 된 대답 의 간단한 수정 IEnumerable<T>
.
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;
}
온라인에서 흥미로운 해결책을 찾았습니다.
예의 : https://improveandrepeat.com/2018/08/a-simple-way-to-shuffle-your-lists-in-c/
var shuffled = myList.OrderBy (x => Guid.NewGuid ()). ToList ();
확실히 오래된 게시물이지만 GUID를 사용합니다.
Items = Items.OrderBy(o => Guid.NewGuid().ToString()).ToList();
GUID는 항상 고유하며 결과가 매번 변경 될 때마다 재생성되므로 GUID는 항상 고유합니다.
이러한 종류의 문제에 대한 매우 간단한 접근 방식은 목록에서 여러 개의 임의 요소 스왑을 사용하는 것입니다.
의사 코드에서 이것은 다음과 같습니다.
do
r1 = randomPositionInList()
r2 = randomPositionInList()
swap elements at index r1 and index r2
for a certain number of times