Cách tốt nhất để ngẫu nhiên một mảng với .NET


141

Cách tốt nhất để ngẫu nhiên hóa một chuỗi các chuỗi với .NET là gì? Mảng của tôi chứa khoảng 500 chuỗi và tôi muốn tạo một chuỗi mới Arrayvới cùng một chuỗi nhưng theo thứ tự ngẫu nhiên.

Vui lòng bao gồm một ví dụ C # trong câu trả lời của bạn.


1
Đây là một giải pháp kỳ lạ nhưng đơn giản cho việc này - stackoverflow.com/a/4262134/1298685 .
Ian Campbell

1
Sử dụng gói NuGet MedallionRandom , đây chỉ là myArray.Shuffled().ToArray()(hoặc myArray.Shuffle()nếu bạn muốn thay đổi mảng hiện tại)
ChaseMedallion

Câu trả lời:


171

Nếu bạn đang sử dụng .NET 3.5, bạn có thể sử dụng tính mát mẻ của IEnountable sau đây (VB.NET, không phải C #, nhưng ý tưởng sẽ rõ ràng ...):

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

Chỉnh sửa: OK và đây là mã VB.NET tương ứng:

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

Chỉnh sửa thứ hai, để đáp lại nhận xét rằng System.Random "không phải là chủ đề an toàn" và "chỉ phù hợp với các ứng dụng đồ chơi" do trả về chuỗi dựa trên thời gian: như được sử dụng trong ví dụ của tôi, Random () hoàn toàn an toàn cho chủ đề, trừ khi bạn đang cho phép thói quen mà bạn chọn ngẫu nhiên mảng được nhập lại, trong trường hợp đó bạn sẽ cần một cái gì đó giống như lock (MyRandomArray)dù sao để không làm hỏng dữ liệu của bạn, điều này cũng sẽ bảo vệ rnd.

Ngoài ra, cần hiểu rõ rằng System.Random là một nguồn entropy không mạnh lắm. Như đã lưu ý trong tài liệu MSDN , bạn nên sử dụng thứ gì đó có nguồn gốc từ System.Security.Cryptography.RandomNumberGeneratornếu bạn đang làm bất cứ điều gì liên quan đến bảo mật. Ví dụ:

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

hai lưu ý: 1) System.Random không an toàn cho luồng (bạn đã được cảnh báo) và 2) System.Random dựa trên thời gian, vì vậy nếu bạn sử dụng mã này trong một hệ thống đồng thời, hai yêu cầu có thể nhận được cùng một giá trị (tức là trong các ứng dụng web)
trị liệu

2
Chỉ cần làm rõ những điều trên, System.Random sẽ tự tạo hạt giống bằng cách sử dụng thời gian hiện tại, do đó hai trường hợp được tạo đồng thời sẽ tạo ra chuỗi "ngẫu nhiên" giống
nhau..System.Random

8
Ngoài ra thuật toán này là O (n log n) và sai lệch bởi thuật toán Qsort. Xem câu trả lời của tôi cho một giải pháp không thiên vị O (n).
Matt Howells

9
Trừ khi OrderBylưu trữ các khóa sắp xếp trong nội bộ, điều này cũng có vấn đề vi phạm thuộc tính bắc cầu của các so sánh theo thứ tự. Nếu có một xác minh chế độ gỡ lỗi OrderBytạo ra kết quả chính xác, thì theo lý thuyết, nó có thể đưa ra một ngoại lệ.
Sam Harwell


205

Việc thực hiện sau đây sử dụng thuật toán Fisher-Yates AKA the Knuth Shuffle. Nó chạy trong thời gian O (n) và xáo trộn tại chỗ, do đó hiệu suất tốt hơn so với kỹ thuật 'sắp xếp theo ngẫu nhiên', mặc dù đó là nhiều dòng mã hơn. Xem ở đây để biết một số phép đo hiệu suất so sánh. Tôi đã sử dụng System.Random, rất tốt cho các mục đích phi mật mã. *

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

Sử dụng:

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

* Đối với các mảng dài hơn, để tạo ra số lượng hoán vị (cực lớn) có thể xảy ra như nhau, cần phải chạy một trình tạo số giả ngẫu nhiên (PRNG) qua nhiều lần lặp cho mỗi lần hoán đổi để tạo ra đủ entropy. Đối với mảng 500 phần tử chỉ một phần rất nhỏ trong số 500 có thể! hoán vị sẽ có thể có được bằng cách sử dụng PRNG. Tuy nhiên, thuật toán Fisher-Yates không thiên vị và do đó việc xáo trộn sẽ tốt như RNG mà bạn sử dụng.


1
Sẽ không tốt hơn để thay đổi các tham số và làm cho việc sử dụng như array.Shuffle(new Random());..?
Ken Kin

Bạn có thể đơn giản hóa việc hoán đổi bằng cách sử dụng Tuples như khung 4.0 -> (mảng [n], mảng [k]) = (mảng [k], mảng [n]);
điện

@Ken Kin: Không, điều này sẽ rất tệ. Lý do là new Random()được khởi tạo với giá trị hạt giống dựa trên thời gian hệ thống hiện tại, chỉ cập nhật sau mỗi ~ 16ms.
Matt Howells

Trong một số thử nghiệm nhanh về điều này so với giải pháp removeAt danh sách, có một sự khác biệt nhỏ ở 999 yếu tố. Sự khác biệt trở nên quyết liệt ở 99999 ints ngẫu nhiên, với giải pháp này là 3ms và khác là 1810ms.
galamdring

18

Bạn đang tìm kiếm một thuật toán xáo trộn, phải không?

Được rồi, có hai cách để làm điều này: thông minh-nhưng-mọi-người-luôn-có-thể-hiểu-hiểu-nó-và-nhận-nó-sai-rất-có-thể-không-biết-sau-tất cả cách, và cách ngu ngốc như đá-nhưng-ai-quan tâm-bởi vì nó hoạt động.

Cách câm

  • Tạo một bản sao của mảng đầu tiên của bạn, nhưng gắn thẻ mỗi chuỗi nên với một số ngẫu nhiên.
  • Sắp xếp các mảng trùng lặp với số ngẫu nhiên.

Thuật toán này hoạt động tốt, nhưng đảm bảo rằng trình tạo số ngẫu nhiên của bạn không có khả năng gắn thẻ hai chuỗi có cùng số. Vì cái gọi là Nghịch lý Sinh nhật , điều này xảy ra thường xuyên hơn bạn mong đợi. Độ phức tạp thời gian của nó là O ( n log n ).

Cách thông minh

Tôi sẽ mô tả đây là một thuật toán đệ quy:

Để xáo trộn một mảng có kích thước n (chỉ số trong phạm vi [0 .. n -1]):

nếu n = 0
  • không làm gì cả
nếu n > 0
  • (bước đệ quy) xáo trộn n -1 phần tử đầu tiên của mảng
  • chọn một chỉ số ngẫu nhiên, x , trong phạm vi [0 .. n -1]
  • hoán đổi phần tử tại chỉ số n -1 với phần tử tại chỉ mục x

Tương đương lặp là đưa một trình vòng lặp đi qua mảng, hoán đổi với các phần tử ngẫu nhiên khi bạn đi cùng, nhưng lưu ý rằng bạn không thể trao đổi với một phần tử sau phần tử mà trình vòng lặp trỏ tới. Đây là một lỗi rất phổ biến và dẫn đến một sự xáo trộn sai lệch.

Độ phức tạp thời gian là O ( n ).


8

Thuật toán này đơn giản nhưng không hiệu quả, O (N 2 ). Tất cả các thuật toán "sắp xếp theo" thường là O (N log N). Nó có thể không tạo ra sự khác biệt dưới hàng trăm ngàn yếu tố nhưng nó sẽ cho các danh sách lớ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);
}

Lý do tại sao đó là O (N 2 ) là tinh tế: List.RemoveAt () là một hoạt động O (N) trừ khi bạn xóa theo thứ tự từ cuối.


2
Điều này có tác dụng tương tự như xáo trộn knuth, nhưng nó không hiệu quả, vì nó liên quan đến việc hủy bỏ một danh sách và lặp lại danh sách khác. Trao đổi vật phẩm tại chỗ sẽ là một giải pháp tốt hơn.
Nick Johnson

1
Tôi thấy điều này thanh lịch và dễ hiểu và trên 500 chuỗi, nó không tạo ra một chút khác biệt ...
Sklivvz

4

Bạn cũng có thể thực hiện một phương pháp mở rộng ra khỏi Matt Howells. Thí dụ.

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

Sau đó, bạn có thể chỉ cần sử dụng nó như:

        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>();

tại sao bạn lại tạo rng mỗi lần gọi phương thức ... Bạn khai báo nó ở cấp lớp nhưng sử dụng nó như một địa phương ...
Yaron

1

Ngẫu nhiên mảng là chuyên sâu khi bạn phải thay đổi xung quanh một chuỗi các chuỗi. Tại sao không chỉ đọc ngẫu nhiên từ mảng? Trong trường hợp xấu nhất, bạn thậm chí có thể tạo một lớp bao bọc với getNextString (). Nếu bạn thực sự cần phải tạo một mảng ngẫu nhiên thì bạn có thể làm một cái gì đó như

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

* 5 là tùy ý.


Một lần đọc ngẫu nhiên từ mảng có khả năng trúng một số mục nhiều lần và bỏ lỡ các mục khác!
Ray Hayes

Thuật toán xáo trộn bị hỏng. Bạn sẽ phải làm cho 5 tùy ý của bạn thực sự rất cao trước khi shuffle của bạn không thiên vị.
Pitarou

Tạo một mảng của các chỉ mục (số nguyên). Xáo trộn các chỉ số. Chỉ cần sử dụng các chỉ mục theo thứ tự ngẫu nhiên đó. Không trùng lặp, không xáo trộn xung quanh các tham chiếu chuỗi trong bộ nhớ (mỗi lần có thể kích hoạt thực tập và những gì không).
Christopher

1

Chỉ cần nghĩ ra khỏi đỉnh đầu của tôi, bạn có thể làm điều này:

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

Tạo một mảng các float ngẫu nhiên hoặc ints có cùng độ dài. Sắp xếp mảng đó và thực hiện các giao dịch hoán đổi tương ứng trên mảng mục tiêu của bạn.

Điều này mang lại một loại thực sự độc lập.


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, giải pháp của bạn là một IComparer tùy chỉnh không an toàn. Các thói quen Sắp xếp yêu cầu bộ so sánh tuân thủ một số yêu cầu để hoạt động đúng. Đầu tiên trong số đó là tính nhất quán. Nếu bộ so sánh được gọi trên cùng một cặp đối tượng, nó phải luôn trả về cùng một kết quả. (sự so sánh cũng phải mang tính bắc cầu).

Việc không đáp ứng các yêu cầu này có thể gây ra bất kỳ số lượng vấn đề nào trong quy trình sắp xếp bao gồm cả khả năng của một vòng lặp vô hạn.

Về các giải pháp liên kết một giá trị số ngẫu nhiên với mỗi mục nhập và sau đó sắp xếp theo giá trị đó, chúng sẽ dẫn đến sai lệch vốn có trong đầu ra bởi vì bất cứ khi nào hai mục nhập được gán cùng một giá trị số, tính ngẫu nhiên của đầu ra sẽ bị tổn hại. (Trong một thói quen sắp xếp "ổn định", bất kỳ đầu tiên nào trong đầu vào sẽ là đầu tiên trong đầu ra. Array.Sort không xảy ra ổn định, nhưng vẫn có sự sai lệch dựa trên phân vùng được thực hiện bởi thuật toán Quicksort).

Bạn cần phải suy nghĩ về mức độ ngẫu nhiên mà bạn yêu cầu. Nếu bạn đang điều hành một trang web poker, nơi bạn cần mức độ ngẫu nhiên về mật mã để bảo vệ chống lại kẻ tấn công xác định, bạn có những yêu cầu rất khác với người chỉ muốn chọn ngẫu nhiên danh sách phát bài hát.

Để xáo trộn danh sách bài hát, không có vấn đề gì khi sử dụng PRNG được tạo mầm (như System.Random). Đối với một trang web poker, nó thậm chí không phải là một tùy chọn và bạn cần suy nghĩ về vấn đề khó khăn hơn nhiều so với bất kỳ ai sẽ làm cho bạn trên stackoverflow. (sử dụng RNG mật mã chỉ là khởi đầu, bạn cần đảm bảo rằng thuật toán của bạn không đưa ra sai lệch, rằng bạn có đủ nguồn entropy và bạn không để lộ bất kỳ trạng thái nội bộ nào có thể ảnh hưởng đến tính ngẫu nhiên tiếp theo).


0

Bài đăng này đã được trả lời khá tốt - sử dụng một triển khai Durstenfeld của shuffle Fisher-Yates cho một kết quả nhanh chóng và không thiên vị. Thậm chí đã có một số triển khai được đăng, mặc dù tôi lưu ý rằng một số thực sự không chính xác.

Tôi đã viết một vài bài đăng trước đây về việc thực hiện xáo trộn toàn bộ và một phần bằng cách sử dụng kỹ thuật này và (liên kết thứ hai này là nơi tôi hy vọng sẽ thêm giá trị) cũng là một bài đăng tiếp theo về cách kiểm tra xem việc triển khai của bạn có thiên vị không , có thể được sử dụng để kiểm tra bất kỳ thuật toán xáo trộn. Bạn có thể thấy ở cuối bài thứ hai, ảnh hưởng của một lỗi đơn giản trong lựa chọn số ngẫu nhiên có thể gây ra.


1
Liên kết của bạn vẫn bị hỏng: /
Wai Ha Lee

0

Ok, đây rõ ràng là một vết sưng từ phía tôi (xin lỗi ...), nhưng tôi thường sử dụng một phương pháp khá chung chung và mật mã.

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 () là một phần mở rộng trên bất kỳ IEnumerable nào, do đó, việc lấy các số từ 0 đến 1000 theo thứ tự ngẫu nhiên trong danh sách có thể được thực hiện với

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

Phương pháp này cũng sẽ không gây bất ngờ khi sắp xếp, vì giá trị sắp xếp được tạo và ghi nhớ chính xác một lần cho mỗi phần tử trong chuỗi.


0

Bạn không cần các thuật toán phức tạp.

Chỉ một dòng đơn giản:

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

Lưu ý rằng chúng tôi cần phải chuyển đổi Arrayđể một Listđầu tiên, nếu bạn không sử dụng Listở nơi đầu tiên.

Ngoài ra, hãy nhớ rằng điều này không hiệu quả cho các mảng rất lớn! Nếu không, nó sạch sẽ và đơn giản.


Lỗi: Toán tử '.' không thể được áp dụng cho toán hạng loại 'void'
hữu ích. Xem

0

Đây là một giải pháp Console hoạt động hoàn chỉnh dựa trên ví dụ được cung cấp tại đây :

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

Đây là một cách đơn giản bằng cách sử dụng 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;
}

Đối với tôi cảm giác như bạn có thể tăng cả hiệu quả và khả năng đọc bằng cách thay vì cố gắng xáo trộn một mảng bằng cách khai báo một mảng thứ hai, tốt hơn hết bạn nên chuyển đổi sang một danh sách, xáo trộn và quay lại một mảng:sortedList = source.ToList().OrderBy(x => generator.Next()).ToArray();
T_D 21/2/2016
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.