#c_sharp #net
Можно ли заполнить трехмерный массив с помощью Random так, чтобы элементы не повторялись?
Ответы
Ответ 1
Итак, напишем метод-расширение для ленивого перемешивания, основанный на ответе @VladD: public static IEnumerableShuffle (this IEnumerable Source) { T[] data = Source.ToArray(); for (int i = data.Length - 1; i > -1; --i) { // Rand - глобальная переменная типа System.Random int newIndex = Rand.Next(i + 1); yield return data[newIndex]; data[newIndex] = data[i]; } } Тогда заполнение массива можно описать так: int[,,] arr = new int[5, 5, 5]; // Сгенерируем последовательность чисел по длине массива, // перемешаем и получим ее энумератор IEnumerator randoms = Enumerable.Range(0, arr.Length).Shuffle().GetEnumerator(); randoms.MoveNext(); // Запустим цикл для каждого измерения массива for (int i = 0; i < arr.GetLength(0); ++i) for (int j = 0; j < arr.GetLength(1); ++j) // В самом глубоком цикле будем также двигать и энумератор for (int k = 0; k < arr.GetLength(2); ++k, randoms.MoveNext()) // Присваиваем текущей ячейке текущее значение энумератора arr[i, j, k] = randoms.Current; Логика такова: Генерируем последовательность чисел по общей длине массива Перемешиваем ее случайным образом Получаем случайную последовательность чисел, не проводя никаких проверок на уникальность каждого нового числа (так как изначально каждое число уже уникально)) Чем этот вариант лучше варианта с генерацией (псевдо)случайного числа и последующей проверкой на то, было ли оно использовано ранее? Представьте, что ГПСЧ 5000 раз выдал нам одно и то же число (или даже проще - выдал числа, которые уже использовались ранее). Да, на практике Вы сие вряд ли встретите, так что ситуация чисто гипотетическая. Но на примере этого гипертрофированного исхода я показываю, что сложность метода трудно предсказать и порой она может превысить ожидаемые пределы В качестве бонуса - заполнение массива любой размерности и любой длины: // Надо бы обозначить Arr как ref-параметр (пусть он итак ссылочный), // дабы можно было ожидать его изменения // Однако тогда его нельзя использовать в лямбдах // Так что положимся на самодокументируемость названия) public static void FillWithRandoms(Array Arr) { // Метод предназначен только для массивов чисел if (Arr.GetType().GetElementType() != typeof(int)) throw new TypeAccessException("This method available only for int arrays!"); // Закэшируем длины всех измерений массива int[] lengths = Enumerable.Range(0, Arr.Rank).Select(x => Arr.GetLength(x)).ToArray(); // Создадим массив индексов по числу измерений int[] indices = new int[Arr.Rank]; // Это место Вам уже знакомо) IEnumerator randoms = Enumerable.Range(0, Arr.Length).Shuffle().GetEnumerator(); randoms.MoveNext(); // Цикл по всей длине массива // Каждую итерацию двигаем как энумератор randoms, так и значения в indices for (int i = 0; i < Arr.Length; ++i, randoms.MoveNext(), MoveNext(Arr.Rank - 1)) Arr.SetValue(randoms.Current, indices); // Рекурсивная функция (храни Боже локальные функции, как мы без них жили-то...), // которая изменяет массив indices, постепенно перебирая индексы всех элементов массива: // 0, 0, 0, ..., 0 // 0, 0, 0, ..., 1 // 0, 0, 0, ..., 2 // ... // 3, 2, 4, ..., 0 // ... void MoveNext(int index) { if (index != -1 && ++indices[index] == lengths[index]) { indices[index] = 0; MoveNext(index - 1); } } } Тогда заполнение Вашего массива сведется к: int[,,] arr = new int[1, 2, 3]; FillWithRandoms(arr); Вот и вся задача ¯\_(ツ)_/¯ Надеюсь, мой ответ помог Вам! Желаю удачи в дальнейших свершениях! Ответ 2
Размерность массива не играет никакой роли. Вам нужно сверяться со значениями, которые уже находятся в массиве или сохранять сгенерированные элементы куда-либо, например в Hashset, и проверять при последуещей генерации был ли элемент получен ранее. Метод генерации будет выглядеть примерно так: static HashSet numbers = new HashSet (); static Random r = new Random(); public static int GenerateUniqueNumber() { while (true) { var n = r.Next(0, 1000); if (!numbers.Contains(n)) { numbers.Add(n); return n; } } } Само заполнение массива будет выглядеть так: var arr = new int[5,5,5]; for (int i = 0; i < 5; i++) { for (int j = 0; j < 5; j++) { for (int k = 0; k < 5; k++) { arr[i, j, k] = GenerateUniqueNumber(); } } } Вариант от @tym32167 с первоначальным заполнением списка случайными значениями и последующим получением с удалением этих значений: var numbers = Enumerable.Range(0, 10000).ToList(); var arr = new int[5, 5, 5]; for (int i = 0; i < 5; i++) { for (int j = 0; j < 5; j++) { for (int k = 0; k < 5; k++) { var randomIndex = r.Next(0, numbers.Count); var number = numbers[randomIndex]; numbers.RemoveAt(randomIndex); arr[i, j, k] = number; } } }
Комментариев нет:
Отправить комментарий