В этом рецепте я расскажу как получить массив фиксированного размера, который заполнен псевдослучайным числами, при этом не будет использован стандартный подход с проходом по созданному массиву с помощью цикла или функциональные шаблоны из стандартной библиотеки. Таким образом, в этой статье я напомню вам про то, что в D есть кое-что интересное, хотя и не столь часто используемое.
Перейдем же к заполнению массива псевдослучайными числами…
Предположим, нам нужен массив фиксированного размера с количеством элементов равным 10, и от этого количества и будем отталкиваться, к тому же при необходимости, вы сможете адаптировать код рецепта под свои нужды.
Сам код выглядит так:
import std.random : uniform; int[10] array; // array[] = 0; array[] += uniform(0, 1000);
Этот код сначала инициализирует массив фиксированного размера, создавая набор нулевых элементов массива (т.е массив из 10 элементов int.init). Принципиально важно, чтобы в момент создания массива, в него попали именно значения вида cast(T) 0, где T — это тип элементов массива. Дело в том, что не все типы инициализируются нулем, особенно если учесть то, что существуют типы с плавающей точкой (инициализируются значением <имя типа>.nan) или же пользовательские числовые типы (могут быть инициализированы практически любым значением).
Далее, вступает в дело, так называемая поэлементная операция над элементами массива, которая в D записывается в нотации среза и представляет собой очень удобное средство для манипуляции массивами с уже известными размерами без непосредственного использования циклов. Такого рода операции подразумевают, что обозначенное некой конструкцией D действие применяется к каждому элементу массива, что на практике означает развертку действия в цикл или в векторную форму (зависит от реализации компилятора). С помощью uniform мы генерируем псевдослучайное число в интервале от 0 до 1000 (тип int) и прибавляем его к текущему элементу массива, который равен нулю, добиваясь этим того, что полученное псевдослучайное значение замещает собой значение из массива, а благодаря указанию [], подобная операция замены распространяется на весь массив!
Теперь когда с рецептом мы закончили, расскажу совсем немного про поэлементные операции…
Поэлементные операции над элементами массива — мощное средство, которое позволяет вам использовать некоторые операции сразу на весь массив без явного указания его элементов. Все, что нужно для применения этого средства языка — это некоторый массив с уже известным количеством элементов, некоторое элементарное действие и указание среза после каждого элементарного действия (нужно указать [] или [m..n], как говорит Александреску).
Обращаю внимание на то, что под результат выражения память уже должна быть выделена, иначе компилятор откажется выполнять развертку операции на весь массив:
auto c = new double[4]; // Память под массив должна быть уже выделена c[] = (a[] + b[]) / 2; // Рассчитать среднее арифметическое a и b assert(c == [ 2.0, 2.5, 3.0, 0.5 ]); // Проверяем
В поэлементных операциях могут участвовать следующие элементы языка:
- элементарное значение, например, некоторое целочисленное значение;
- срез элементов массива, как без указания границ среза так и с их указанием, например, [] или [0..$/3];
- любое корректное выражение D с использованием унарных операций — и ~; бинарных операторов +,—,*,/,%,^^,^,&,|,=,+=,-=,*=,/=,%=,^=,&= и |=.
В общем, советую прочитать про поэлементные операции в книге Андрея Александреску «Язык программирования D» в разделе 4.1.7.
(Честно, взял у Александреску описание (глава 4.1.7 книги «Язык программирования D»), но и сам кое-что проверил: выяснилось, что так можно даже массивы складывать без циклов и не только, а еще и использовать некоторые функции стандартной библиотеки. Это безусловно заслуживает отдельного исследования, за которое мы быть может и возьмемся).