std.container

Этот модуль определяет типовые контейнеры.

Построение: Для реализации различных контейнеров были использованы подходы как на основе структуры, так и класса. Использование std.container.util.make даёт единообразное построение с любым подходом.

import std.container;
// Строит красное-черное дерево и массив, каждый из которых содержит значения 1, 2, 3.
// RedBlackTree обычно должно распределять память, используя `new`
RedBlackTree!int rbTree = new RedBlackTree!int(1, 2, 3);
// Но `new` не должно использоваться с Array
Array!int array = Array!int(1, 2, 3);
// `make` скрывает различия
RedBlackTree!int rbTree2 = make!(RedBlackTree!int)(1, 2, 3);
Array!int array2 = make!(Array!int)(1, 2, 3);
Заметьте, что make может сделать вывод о типе элементов, исходя из полученных аргументов.
import std.container;
auto rbTree = make!RedBlackTree(1, 2, 3); // RedBlackTree!int
auto array = make!Array("1", "2", "3"); // Array!string

Ссылочная семантика: Все контейнеры имеют ссылочную семантику, и это означает, что после присвоения, две переменные ссылаются на одни и те же лежащие в основе данные.

Для создания копии контейнера используйте контейнерный примитив c.dup.
import std.container, std.range;
Array!int originalArray = make!(Array!int)(1, 2, 3);
Array!int secondArray = originalArray;
assert(equal(originalArray[], secondArray[]));

// изменение одного экземпляра изменяет также другой!
originalArray[0] = 12;
assert(secondArray[0] == 12);

// secondArray теперь ссылается на независимую копию originalArray
secondArray = originalArray.dup;
secondArray[0] = 1;
// утверждается, что originalArray не был изменён
assert(originalArray[0] == 12);
Внимание: Если контейнер реализован как класс, использование неинициализированного экземпляра может вызвать разыменование указателя на null.
import std.container;

RedBlackTree!int rbTree;
rbTree.insert(5); // Ошибка! Разыменование указателя на null.
Использование неинициализированного контейнера, основанного на структуре, работает, поскольку структура сама инициализирует себя при использовании; тем не менее, до этого момента, контейнер не будет иметь идентичности, и присвоение не создаст две ссылки на одни и те же данные.
import std.container;

// создание неинициализированного массива
Array!int array1;
// array2 НЕ ссылается на array1
Array!int array2 = array1;
array2.insertBack(42);
// таким образом, на array1 эффект не действует
assert(array1.empty);

// после инициализации ссылочная семантика работает как ожидалось
array1 = array2;
// теперь воздействие влияет также и на array2 
array1.removeBack();
assert(array2.empty);
Следовательно, рекомендуется при создании контейнеров всегда использовать std.container.util.make.
При необходимости можно помещать контейнеры в другой контейнер. Например, для создания массива Array из десяти пустых массивов, используйте следующий подход, который вызовет make десять раз.
import std.container, std.range;

auto arrOfArrs = make!Array(generate!(() => make!(Array!int)).take(10));

Подмодули: Этот модуль состоит из следующих подмодулей:

Первичный диапазон контейнера: В то время как некоторые контейнеры предлагают прямой доступ к своим элементам, например, с помощью opIndex, c.front или c.back, доступ к содержимому контейнера и его модификация, как правило, осуществляется через тип его первичного диапазона, который объявлен через псевдоним C.Range. Например, тип первичного диапазона Array!int – это Array!int.Range.

Если в документации метод контейнера принимает параметр типа Range, то он относится к типу первичного диапазона этого контейнера. Нередко используется Take!Range, в этом случае диапазон ссылается на диапазон элементов в контейнере. Аргументы этих параметров должны быть получены из того же экземпляра контейнера, так как они работают только с ним. Важно отметить, что множество типовых алгоритмов, работающих с диапазонами, возвращают тот же тип диапазона, что и их входной диапазон.
import std.algorithm.comparison : equal;
import std.algorithm.iteration : find;
import std.container;
import std.range : take;

auto array = make!Array(1, 2, 3);

// `find` возвращает Array!int.Range, продвинутый к элементу "2"
array.linearRemove(array[].find(2));

assert(array[].equal([1]));

array = make!Array(1, 2, 3);

// диапазон, переданный в `linearRemove` – это Take!(Array!int.Range),
// перекрывающий именно элемент "2"
array.linearRemove(array[].find(2).take(1));

assert(array[].equal([1, 3]));
Когда любой диапазон может быть передан в метод в качестве аргумента, документация обычно ссылается на параметр шаблонного типа как Stuff.
import std.algorithm.comparison : equal;
import std.container;
import std.range : iota;

auto array = make!Array(1, 2);

// тип диапазона, возвращаемый функцией `iota` не имеет никакого отношения к Array,
// но отлично подходит для Array.insertBack:
array.insertBack(iota(3, 10));

assert(array[].equal([1, 2, 3, 4, 5, 6, 7, 8, 9]));

Примитивы контейнеров: Контейнеры не формируют иерархию классов, вместо этого они реализуют общий набор примитивов (смотрите таблицу внизу). Каждый из этих примитивов гарантирует специфическую сложность для самого неблагоприятного случая, и, таким образом, позволяет писать обобщённый код, независимый от реализации контейнера.

Например, примитивы c.remove(r) и c.linearRemove(r) удаляют последовательность элементов в диапазоне r из контейнера c. Примитив c.remove(r) гарантирует сложность Ο(nr log nc) в неблагоприятном случае, а c.linearRemove(r) смягчает эту гарантию до Ο(nc).
Поскольку последовательность элементов можно удалить из двусвязного списка за константное время, DList предоставляет примитив c.remove(r), также как и c.linearRemove(r). С другой стороны, Array предлагает только c.linearRemove(r).
Следующая таблица описывает общий набор примитивов, реализуемых контейнерами. Контейнер не обязан реализовывать все примитивы, но если примитив реализован, он должен поддерживать синтаксис, описанный в столбце Синтаксис, с семантикой, упомянутой в столбце Описание, и он не должен иметь сложность для неблагоприятного случая хуже, чем записанная в нотации большого O столбце Ο(·). Здесь C означает тип контейнера, c – это экземпляр типа контейнера, nx представляет эффективную длину значения x, который может быть единственным элементом (в этом случае nx – это 1), контейнером, или диапазоном.
Примитивы контейнера
Синтаксис Ο(·) Описание
C(x) nx Создаёт контейнер типа C из другого контейнера или из диапазона. Созданный контейнер не должен быть ссылкой на null, даже если x было пустым.
c.dup nc Возвращает дубликат контейнера.
c ~ x nc + nx Возвращает конкатенацию c и r. x может быть единичным элементом или входным диапазоном.
x ~ c nc + nx Возвращает конкатенацию x и c. x может быть единичным элементом или типом входного диапазона.
    Итерирование
c.Range Тип первичного диапазона, связанный с контейнером.
c[] log nc Возвращает диапазон, итерирующий по всему контейнеру, в порядке, определённом в контейнере.
c[a .. b] log nc Выбирает часть контейнера от ключа a до ключа b.
    Ёмкость
c.empty 1 Возвращает true, если контейнер не имеет элементов, false в противном случае.
c.length log nc Возвращает количество элементов в контейнере.
c.length = n nc + n Принудительно устанавливает количество элементов в контейнере на n. Если контейнер в итоге растёт, дополнительные элементы инициализируются контейнеро-зависимым образом (обычно с помощью T.init).
c.capacity log nc Возвращает максимальное количество элементов, которые можно сохранить в контейнере, не запуская перераспределение памяти.
c.reserve(x) nc Приводит capacity к значению, равному по крайней мере x, без его уменьшения.
    Доступ
c.front log nc Возвращает первый элемент контейнера в порядке, определяемом контейнером.
c.moveFront log nc Разрушительно читает и возвращает первый элемент контейнера. Место, которое он занимал, не удаляется из контейнера; оно остаётся инициализированным через T.init. Эта процедура не должна быть определена, если front возвращает ref.
c.front = v log nc Присваивает v первому элементу контейнера.
c.back log nc Возвращает последний элемент контейнера в порядке, определяемом контейнером.
c.moveBack log nc Разрушительно читает и возвращает последний элемент контейнера. Место, которое он занимал, не удаляется из контейнера; оно остаётся инициализированным через T.init. Эта процедура не должна быть определена, если front возвращает ref.
c.back = v log nc Присваивает v последнему элементу контейнера.
c[x] log nc Предоставляет доступ к контейнеру по индексу. Тип индекса определяется контейнером. Контейнер может определить несколько типов индексов (и, следовательно, перегрузить индексирование).
c.moveAt(x) log nc Разрушительно читает и возвращает значение в позиции x. Место, которое он занимал, не удаляется из контейнера; оно остаётся инициализированным через T.init.
c[x] = v log nc Устанавливает элемент в контейнер в позицию, указанную индексом.
c[x] op= v log nc Выполняет операцию чтения-модификации-записи по определённому индексу в контейнере.
    Операции
e in c log nc Возвращает не ноль, если e найдено в c.
c.lowerBound(v) log nc Возвращает диапазон всех элементов, строго меньших, чем v.
c.upperBound(v) log nc Возвращает диапазон всех элементов, строго больших, чем v.
c.equalRange(v) log nc Возвращает диапазон всех элементов в c, которые равняются v.
    Изменения
c ~= x nc + nx Добавление x к c. x может быть единственным элементом или типом входного диапазона.
c.clear() nc Удаляет все элементы из c.
c.insert(x) nx * log nc Вставляет x в c в позицию (или позиции), выбранную c.
c.stableInsert(x) nx * log nc То же, что c.insert(x), но гарантированно не нарушает каких-либо диапазонов.
c.linearInsert(v) nc То же, что c.insert(v), но смягчает сложность до линейной.
c.stableLinearInsert(v) nc То же, что c.stableInsert(v), но смягчает сложность до линейной.
c.removeAny() log nc Удаляет некоторый элемент из c и возвращает его.
c.stableRemoveAny() log nc То же, что c.removeAny(), но гарантированно не нарушает каких-либо итераторов.
c.insertFront(v) log nc Вставляет v с передней стороны c.
c.stableInsertFront(v) log nc То же, что c.insertFront(v), но гарантирует, что никакие диапазоны не станут недействительными.
c.insertBack(v) log nc Вставляет v с задней стороны c.
c.stableInsertBack(v) log nc То же, что c.insertBack(v), но гарантирует, что никакие диапазоны не станут недействительными.
c.removeFront() log nc Удаляет элемент с передней стороны c.
c.stableRemoveFront() log nc То же, что c.removeFront(), но гарантирует, что никакие диапазоны не станут недействительными.
c.removeBack() log nc Удаляет значение с обратной стороны c.
c.stableRemoveBack() log nc То же, что c.removeBack(), но гарантирует, что никакие диапазоны не станут недействительными.
c.remove(r) nr * log nc Удаляет диапазон r из c.
c.stableRemove(r) nr * log nc То же, что c.remove(r), но гарантирует, что итераторы не станут недействительными.
c.linearRemove(r) nc Удаляет диапазон r из c.
c.stableLinearRemove(r) nc То же, что c.linearRemove(r), но гарантирует, что итераторы не станут недействительными.
c.removeKey(k) log nc Удаляет элемент из c, используя ключ k. Тип ключа определяется контейнером.

Исходный код: std/container/package.d

Лицензия:
Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at boost.org/LICENSE_1_0.txt).
Авторы:
Steven Schveighoffer, Andrei Alexandrescu