[C#] 리스트 랜덤 화 <T>

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