.NET으로 배열을 무작위 화하는 가장 좋은 방법


141

.NET으로 문자열 배열을 무작위 화하는 가장 좋은 방법은 무엇입니까? 내 배열에는 약 500 개의 문자열이 포함되어 Array있으며 동일한 문자열을 사용하지만 임의의 순서 로 새 문자열 을 만들고 싶습니다 .

답변에 C # 예를 포함하십시오.


1
여기에 이상하지만 간단한 해결책이 있습니다 -stackoverflow.com/a/4262134/1298685 .
Ian Campbell

1
MedallionRandom NuGet 패키지를 사용하면 이 패키지는 단지 myArray.Shuffled().ToArray()(또는 myArray.Shuffle()현재 배열을 변경하려는 경우)
ChaseMedallion

답변:


171

.NET 3.5를 사용하는 경우 다음 IEnumerable coolness (C #이 아닌 VB.NET을 사용할 수 있지만 아이디어는 명확해야합니다 ...)를 사용할 수 있습니다.

Random rnd=new Random();
string[] MyRandomArray = MyArray.OrderBy(x => rnd.Next()).ToArray();    

편집 : 확인 및 해당 VB.NET 코드는 다음과 같습니다.

Dim rnd As New System.Random
Dim MyRandomArray = MyArray.OrderBy(Function() rnd.Next()).ToArray()

두 번째 편집은 시간 기반 시퀀스를 반환하여 System.Random이 "스레드 안전하지 않고" "장난감 앱에만 적합"하다는 설명에 응답합니다. 배열을 무작위 화하는 루틴을 다시 입력하도록 허용합니다.이 경우 lock (MyRandomArray)데이터를 손상시키지 않기 위해 어쨌든 같은 것이 필요 rnd합니다.

또한 엔트로피의 원천 인 System.Random이 그다지 강력하지 않다는 것을 잘 이해하고 있어야합니다. MSDN 문서에 언급 된대로 System.Security.Cryptography.RandomNumberGenerator보안 관련 작업을 수행하는 경우 파생 된 항목을 사용해야합니다. 예를 들면 다음과 같습니다.

using System.Security.Cryptography;

...

RNGCryptoServiceProvider rnd = new RNGCryptoServiceProvider();
string[] MyRandomArray = MyArray.OrderBy(x => GetNextInt32(rnd)).ToArray();

...

static int GetNextInt32(RNGCryptoServiceProvider rnd)
    {
        byte[] randomInt = new byte[4];
        rnd.GetBytes(randomInt);
        return Convert.ToInt32(randomInt[0]);
    }

두 가지 참고 사항 : 1) System.Random은 스레드로부터 안전하지 않으며 (경고를 받았습니다) 2) System.Random은 시간 기반이므로이 코드를 많은 동시 시스템에서 사용하는 경우 두 요청이 같은 가치 (즉 webapps에서)
therealhoff

2
그냥 위를 명확히하기 위해, System.Random는 현재 시간을 사용하여 자신을 배정되므로, 같은 "임의"sequence..System.Random을 생성합니다 동시에 생성 된 두 개의 인스턴스는 장난감 애플리케이션에 사용되어야한다
therealhoff

8
또한이 알고리즘은 O (n log n)이며 Qsort 알고리즘에 의해 바이어스됩니다. O (n) 편견없는 솔루션에 대한 내 대답을 참조하십시오.
매트 Howells

9
OrderBy정렬 키를 내부적으로 캐시 하지 않으면 정렬 된 비교의 전이 속성을 위반하는 문제도 있습니다. OrderBy올바른 결과 를 생성 한 디버그 모드 확인이 있으면 이론상 예외가 발생할 수 있습니다.
Sam Harwell


205

다음 구현은 Fisher-Yates 알고리즘 ( 일명 Knuth Shuffle)을 사용합니다. 그것은 O (n) 시간에 실행되고 그 뒤를 뒤섞습니다. 그래서 더 많은 코드 줄이지 만 '랜덤 정렬'기법보다 성능이 좋습니다. 비교 성능 측정에 대해서는 여기 를 참조 하십시오 . 암호화 이외의 용도로는 괜찮은 System.Random을 사용했습니다. *

static class RandomExtensions
{
    public static void Shuffle<T> (this Random rng, T[] array)
    {
        int n = array.Length;
        while (n > 1) 
        {
            int k = rng.Next(n--);
            T temp = array[n];
            array[n] = array[k];
            array[k] = temp;
        }
    }
}

용법:

var array = new int[] {1, 2, 3, 4};
var rng = new Random();
rng.Shuffle(array);
rng.Shuffle(array); // different order from first call to Shuffle

* 배열이 길수록 (매우 큰) 순열 수를 동일하게 만들려면 각 스왑마다 많은 반복을 통해 의사 난수 생성기 (PRNG)를 실행하여 충분한 엔트로피를 생성해야합니다. 500 원소 배열의 경우 가능한 500의 아주 작은 부분 만! PRNG를 사용하여 순열을 얻을 수 있습니다. 그럼에도 불구하고 Fisher-Yates 알고리즘은 편견이 없으므로 셔플은 사용하는 RNG만큼 좋습니다.


1
매개 변수를 변경하고 array.Shuffle(new Random());.. 와 같은 사용법을 만드는 것이 낫지 않습니까?
Ken Kin

프레임 워크 4.0-> (array [n], array [k]) = (array [k], array [n])에서 Tuples를 사용하여 스왑을 단순화 할 수 있습니다.
dynamichael 2016 년

@ Ken Kin : 아니오, 이것은 나쁠 것입니다. 그 이유는 new Random()현재 시스템 시간을 기준으로 시드 값으로 초기화되어 ~ 16ms마다 업데이트되기 때문입니다.
Matt Howells

이 목록과 removeAt 솔루션의 빠른 테스트에서는 999 개의 요소에서 약간의 차이가 있습니다. 이 솔루션은 3ms, 다른 하나는 1810ms로 99999 임의 정수로 차이가 심해집니다.
galamdring

18

셔플 링 알고리즘을 찾고 있습니까?

이 작업을 수행하는 두 가지 방법이 있습니다. 영리하지만 항상 사람들은 항상 잘못 이해하고 잘못 이해하고 잘못 이해하면 그다지 영리하지 않습니다. 방법, 그리고 바위처럼 멍청하지만 누가 돌봐주는 방식으로 작동합니다.

멍청한 길

  • 첫 번째 배열의 복제본을 생성하지만 각 문자열에 임의의 숫자로 태그를 지정하십시오.
  • 난수를 기준으로 중복 배열을 정렬합니다.

이 알고리즘은 잘 작동하지만 난수 생성기가 두 개의 문자열에 같은 숫자를 태그하지 않도록하십시오. 소위 Birthday Paradox로 인해 예상보다 자주 발생합니다. 시간 복잡도는 O ( n log n )입니다.

영리한 방법

이것을 재귀 알고리즘으로 설명하겠습니다.

크기가 n 인 배열을 섞으려면 ([0 .. n -1] 범위에 있음 ) :

만약 N = 0
  • 아무것도하지 마세요
만약 N > 0
  • (재귀 단계) 배열 의 첫 번째 n -1 요소를 섞습니다.
  • [0 .. n -1] 범위에서 랜덤 인덱스 x를 선택하십시오 .
  • 인덱스 n -1의 요소를 인덱스 x 의 요소와 교체

반복되는 동등 물은 반복자를 배열을 통해 걷는 것으로, 임의의 요소로 바꾸면서 반복자가 가리키는 요소 뒤에는 요소를 바꿀 수 없다는 점에 유의하십시오. 이것은 매우 일반적인 실수이며 편향된 셔플로 이어집니다.

시간 복잡도는 O ( n )입니다.


8

이 알고리즘은 간단하지만 효율적이지 않습니다. O (N 2 ). 모든 "순서 순"알고리즘은 일반적으로 O (N log N)입니다. 아마도 수십만 요소 이하에서 차이를 만들지 않지만 큰 목록에는 영향을 미칩니다.

var stringlist = ... // add your values to stringlist

var r = new Random();

var res = new List<string>(stringlist.Count);

while (stringlist.Count >0)
{
   var i = r.Next(stringlist.Count);
   res.Add(stringlist[i]);
   stringlist.RemoveAt(i);
}

그것이 O (N 2 ) 인 이유 는 미묘합니다. List.RemoveAt () 는 끝에서 순서대로 제거하지 않는 한 O (N) 연산입니다.


2
이것은 knuth shuffle과 같은 효과를 갖지만 하나의 목록을 제거하고 다른 목록을 다시 채우기 때문에 효율적이지 않습니다. 아이템을 교체하는 것이 더 나은 솔루션입니다.
Nick Johnson

1
나는이 우아하고 쉽게 이해할 수 있고 500 줄에서 약간의 차이를 만들지 않는다는 것을
알았습니다

4

Matt Howells에서 확장 방법을 만들 수도 있습니다. 예.

   namespace System
    {
        public static class MSSystemExtenstions
        {
            private static Random rng = new Random();
            public static void Shuffle<T>(this T[] array)
            {
                rng = new Random();
                int n = array.Length;
                while (n > 1)
                {
                    int k = rng.Next(n);
                    n--;
                    T temp = array[n];
                    array[n] = array[k];
                    array[k] = temp;
                }
            }
        }
    }

그런 다음 다음과 같이 사용할 수 있습니다.

        string[] names = new string[] {
                "Aaron Moline1", 
                "Aaron Moline2", 
                "Aaron Moline3", 
                "Aaron Moline4", 
                "Aaron Moline5", 
                "Aaron Moline6", 
                "Aaron Moline7", 
                "Aaron Moline8", 
                "Aaron Moline9", 
            };
        names.Shuffle<string>();

왜 메소드에 대한 모든 호출을 rng로 다시 작성합니까? 클래스 레벨에서 선언하지만 로컬로 사용합니다.
Yaron

1

많은 문자열 주위를 이동해야하므로 배열을 무작위로 지정하는 것이 중요합니다. 왜 배열에서 무작위로 읽지 않습니까? 최악의 경우 getNextString ()을 사용하여 래퍼 클래스를 만들 수도 있습니다. 실제로 임의의 배열을 만들어야하는 경우 다음과 같은 작업을 수행 할 수 있습니다

for i = 0 -> i= array.length * 5
   swap two strings in random places

* 5는 임의입니다.


배열에서 임의의 읽기는 일부 항목을 여러 번 누르고 다른 항목을 놓칠 수 있습니다!
Ray Hayes

셔플 알고리즘이 깨졌습니다. 셔플이 불편 해지기 전에 임의의 5를 매우 높게 만들어야합니다.
Pitarou

(정수) 인덱스의 배열을 만듭니다. 인덱스를 섞습니다. 무작위 순서로 색인을 사용하십시오. 메모리에서 문자열 참조를 뒤섞 지 않고 복제하지 않습니다 (각각 인터 닝을 유발할 수도 있고 그렇지 않을 수도 있습니다).
Christopher

1

내 머리 꼭대기에서 생각하면 다음과 같이 할 수 있습니다.

public string[] Randomize(string[] input)
{
  List<string> inputList = input.ToList();
  string[] output = new string[input.Length];
  Random randomizer = new Random();
  int i = 0;

  while (inputList.Count > 0)
  {
    int index = r.Next(inputList.Count);
    output[i++] = inputList[index];
    inputList.RemoveAt(index);
  }

  return (output);
}

0

길이가 같은 임의의 부동 소수점 또는 정수의 배열을 생성하십시오. 해당 배열을 정렬하고 대상 배열에서 해당 스왑을 수행하십시오.

이것은 진정으로 독립적 인 정렬을 생성합니다.


0
Random r = new Random();
List<string> list = new List(originalArray);
List<string> randomStrings = new List();

while(list.Count > 0)
{
int i = r.Random(list.Count);
randomStrings.Add(list[i]);
list.RemoveAt(i);
}

0

Jacco, 맞춤형 IComparer를 사용하는 솔루션은 안전하지 않습니다. 정렬 루틴을 제대로 수행하려면 비교기가 여러 요구 사항을 준수해야합니다. 그중 첫 번째는 일관성입니다. 비교기가 동일한 쌍의 객체에서 호출되면 항상 동일한 결과를 반환해야합니다. (비교도 전 이적이어야 함).

이러한 요구 사항을 충족하지 않으면 무한 루프 가능성을 포함하여 정렬 루틴에 많은 문제가 발생할 수 있습니다.

임의의 숫자 값을 각 항목과 연결 한 다음 해당 값을 기준으로 정렬하는 솔루션과 관련하여 두 항목에 동일한 숫자 값이 할당 될 때마다 출력의 임의성이 손상됩니다. "안정한"정렬 루틴에서, 입력의 첫 번째 중 하나가 출력에서 ​​첫 번째입니다. Array.Sort는 안정적이지 않지만 Quicksort 알고리즘에 의해 수행 된 분할에 기반한 편향이 여전히 있습니다.

어느 수준의 무작위성이 필요한지 생각해야합니다. 결정된 공격자로부터 보호하기 위해 암호화 수준의 임의성이 필요한 포커 사이트를 운영하는 경우 노래 재생 목록을 무작위 화하려는 사람과는 매우 다른 요구 사항이 있습니다.

노래 목록 셔플 링의 경우 시드 된 PRNG (System.Random과 같은)를 사용하는 데 아무런 문제가 없습니다. 포커 사이트의 경우 옵션이 아니며 스택 오버 플로우에서 누군가 당신을 위해 할 것보다 훨씬 더 어려운 문제에 대해 생각해야합니다. (암호화 RNG를 사용하는 것은 시작에 불과하며, 알고리즘에 편향이 발생하지 않도록하고, 엔트로피가 충분하고, 후속 임의성을 손상시키는 내부 상태를 노출시키지 않아야합니다.)


0

이 게시물은 이미 꽤 잘 대답되어 있습니다. Fisher-Yates 셔플의 Durstenfeld 구현을 사용하여 빠르고 편견없는 결과를 얻으십시오. 일부 구현이 실제로 잘못 되었음에도 불구하고 일부 구현이 게시되었습니다.

나는 게시물에 대해 잠시 뒤로 몇 쓴 이 기술을 사용하여 전체 및 부분 셔플을 구현 도 (I 값을 추가 바라고 곳이 두 번째 링크입니다) 등을 구현 편견 여부를 확인하는 방법에 대한 후속 포스트를 , 셔플 알고리즘을 확인하는 데 사용할 수 있습니다. 두 번째 게시물의 끝 부분에서 난수 선택에서 간단한 실수의 영향을 볼 수 있습니다.


1
귀하의 링크가 여전히 끊어졌습니다 : /
Wai Ha Lee

0

좋아, 이것은 분명히 내 편과 충돌하지만 (사죄 ...), 나는 종종 매우 일반적이고 암호로 강력한 방법을 사용합니다.

public static class EnumerableExtensions
{
    static readonly RNGCryptoServiceProvider RngCryptoServiceProvider = new RNGCryptoServiceProvider();
    public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> enumerable)
    {
        var randomIntegerBuffer = new byte[4];
        Func<int> rand = () =>
                             {
                                 RngCryptoServiceProvider.GetBytes(randomIntegerBuffer);
                                 return BitConverter.ToInt32(randomIntegerBuffer, 0);
                             };
        return from item in enumerable
               let rec = new {item, rnd = rand()}
               orderby rec.rnd
               select rec.item;
    }
}

Shuffle ()은 IEnumerable의 확장이므로 목록에서 임의의 순서로 0에서 1000까지의 숫자를 얻을 수 있습니다.

Enumerable.Range(0,1000).Shuffle().ToList()

정렬 방법은 정렬 값이 생성되고 시퀀스의 요소 당 정확히 한 번만 기억되므로 정렬과 관련하여 놀라지 않을 것입니다.


0

복잡한 알고리즘이 필요하지 않습니다.

간단한 한 줄만 :

Random random = new Random();
array.ToList().Sort((x, y) => random.Next(-1, 1)).ToArray();

를로 변환해야 Array합니다.List 사용하지 않는 경우, 먼저 List처음에.

또한 이것은 매우 큰 배열에는 효율적이지 않습니다! 그렇지 않으면 깨끗하고 간단합니다.


오류 : '.'연산자 '무효'유형 오퍼랜드에 적용 할 수 없다
usefulBee

0

이것은 다음에 제공된 예제를 기반으로 한 완전한 콘솔 솔루션입니다 .

class Program
{
    static string[] words1 = new string[] { "brown", "jumped", "the", "fox", "quick" };

    static void Main()
    {
        var result = Shuffle(words1);
        foreach (var i in result)
        {
            Console.Write(i + " ");
        }
        Console.ReadKey();
    }

   static string[] Shuffle(string[] wordArray) {
        Random random = new Random();
        for (int i = wordArray.Length - 1; i > 0; i--)
        {
            int swapIndex = random.Next(i + 1);
            string temp = wordArray[i];
            wordArray[i] = wordArray[swapIndex];
            wordArray[swapIndex] = temp;
        }
        return wordArray;
    }         
}

0
        int[] numbers = {0,1,2,3,4,5,6,7,8,9};
        List<int> numList = new List<int>();
        numList.AddRange(numbers);

        Console.WriteLine("Original Order");
        for (int i = 0; i < numList.Count; i++)
        {
            Console.Write(String.Format("{0} ",numList[i]));
        }

        Random random = new Random();
        Console.WriteLine("\n\nRandom Order");
        for (int i = 0; i < numList.Capacity; i++)
        {
            int randomIndex = random.Next(numList.Count);
            Console.Write(String.Format("{0} ", numList[randomIndex]));
            numList.RemoveAt(randomIndex);
        }
        Console.ReadLine();

-1

OLINQ를 사용하는 간단한 방법은 다음과 같습니다.

// Input array
List<String> lst = new List<string>();
for (int i = 0; i < 500; i += 1) lst.Add(i.ToString());

// Output array
List<String> lstRandom = new List<string>();

// Randomize
Random rnd = new Random();
lstRandom.AddRange(from s in lst orderby rnd.Next(100) select s);

-2
private ArrayList ShuffleArrayList(ArrayList source)
{
    ArrayList sortedList = new ArrayList();
    Random generator = new Random();

    while (source.Count > 0)
    {
        int position = generator.Next(source.Count);
        sortedList.Add(source[position]);
        source.RemoveAt(position);
    }  
    return sortedList;
}

나에게 두 번째 배열을 선언하여 배열을 뒤섞는 대신 효율성과 readablilty를 증가시킬 수 있다고 생각합니다. List, Shuffle로 변환 한 다음 배열로 다시 변환하는 것이 좋습니다.sortedList = source.ToList().OrderBy(x => generator.Next()).ToArray();
T_D
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.