Модуль Dynarray
module Dynarray: sig .. end
Динамические массивы.
Модуль Array предоставляет массивы фиксированной длины. Dynarray предоставляет массивы, длина которых может меняться со временем, путем добавления или удаления элементов в конце массива.
Это обычно используется для накопления элементов, количество которых неизвестно заранее или изменяется во время вычислений, а также обеспечивает быстрый доступ к элементам в произвольных позициях.
let dynarray_of_list li =
let arr = Dynarray.create () in
List.iter (fun v -> Dynarray.add_last arr v) li;
arr
Модуль Buffer предоставляет аналогичные функции, но он специализируется на накоплении символов в динамически изменяющейся строке.
Модуль Stack предоставляет структуру данных «последним вошел — первым вышел», которую можно легко реализовать на основе динамических массивов.
Предупреждение. В текущей реализации структура памяти динамических массивов отличается от структуры памяти Arrays. См. раздел Структура памяти для получения дополнительной информации.
- С момента 5.2
- Предупреждение unsynchronized_access. Несинхронизированные обращения к динамическим массивам являются программистской ошибкой.
Несинхронизированные обращения
Одновременные обращения к динамическим массивам должны быть синхронизированы (например, с помощью Mutex.t). Несинхронизированные обращения к динамическому массиву являются программистской ошибкой, которая может привести к некорректному состоянию динамического массива, в котором некоторые операции завершатся с исключением Invalid_argument.
Динамические массивы
type !'a t
Динамический массив, содержащий значения типа 'a.
Динамический массив a обеспечивает операции с постоянным временем get и set для индексов от 0 до Dynarray.length a - 1 включительно. Его Dynarray.length может изменяться со временем путем добавления или удаления элементов в конец массива.
Мы говорим, что индекс в dynarray a является допустимым, если он находится в 0 .. length a - 1, и недопустимым в противном случае.
val create : unit -> 'a t
create () — это новый, пустой массив.
val make : int -> 'a -> 'a t
make n x — это новый массив длиной n, заполненный x.
-
Возбуждает
Invalid_argumentеслиn < 0илиn > Sys.max_array_length.
val init : int -> (int -> 'a) -> 'a t
init n f — это новый массив a длиной n, такой, что get a i — это f i.
Другими словами, элементы a — это f 0, затем f 1, затем f 2... и f (n - 1) последний, вычисляются в этом порядке.
Это аналогично Array.init.
-
Возбуждает
Invalid_argumentеслиn < 0илиn > Sys.max_array_length.
val get : 'a t -> int -> 'a
get a i — это i-й элемент a, начиная с индекса 0.
-
Возбуждает
Invalid_argumentесли индекс недопустим
val set : 'a t -> int -> 'a -> unit
set a i x устанавливает i-й элемент a в x.
i должен быть допустимым индексом. set не добавляет новые элементы в массив — см. Dynarray.add_last для добавления элемента.
-
Возбуждает
Invalid_argumentесли индекс недопустим.
val length : 'a t -> int
length a — это количество элементов в массиве.
val is_empty : 'a t -> bool
is_empty a равно true если a пуст, то есть, если length a = 0.
val get_last : 'a t -> 'a
get_last a — это элемент a с индексом length a - 1.
-
Возбуждает
Invalid_argumentеслиaпуст.
val find_last : 'a t -> 'a option
find_last a равно None если a пуст и Some (get_last a) в противном случае.
val copy : 'a t -> 'a t
copy a — это неглубокая копия a, новый массив, содержащий те же элементы, что и a.
Добавление элементов
Примечание: все операции добавления элементов возбуждают Invalid_argument если длина должна увеличиться до Sys.max_array_length.
val add_last : 'a t -> 'a -> unit
add_last a x добавляет элемент x в конец массива a.
val append_array : 'a t -> 'a array -> unit
append_array a b добавляет все элементы b в конец a, в порядке их появления в b.
Например:
let a = Dynarray.of_list [1;2] in
Dynarray.append_array a [|3; 4|];
assert (Dynarray.to_list a = [1; 2; 3; 4])
val append_list : 'a t -> 'a list -> unit
Как Dynarray.append_array, но со списком.
val append : 'a t -> 'a t -> unit
append a b — это как append_array a b, но b — это сам динамический массив, а не массив фиксированной длины.
Предупреждение: append a a — это программистская ошибка, потому что она итерируется по a и добавляет элементы к нему одновременно — см. раздел Итерация ниже. Она завершается с Invalid_argument. Если вы действительно хотите добавить копию a к себе, вы можете использовать Dynarray.append_array a (Dynarray.to_array a) , которая копирует a в временный массив.
val append_seq : 'a t -> 'a Seq.t -> unit
Как Dynarray.append_array, но со списком.
Предупреждение: append_seq a (to_seq_reentrant a) одновременно проходит по a и добавляет элементы в него; порядок этих операций не определен и может привести к бесконечной петле — новые элементы могут, в свою очередь, быть созданы to_seq_reentrant a и снова добавляться.
val append_iter : 'a t -> (('a -> unit) -> 'x -> unit) -> 'x -> unit
append_iter a iter x добавляет каждый элемент x в конец a. Это iter (add_last a) x.
Например, append_iter a List.iter [1;2;3] добавит элементы 1, 2, а затем 3 в конец a. append_iter a Queue.iter q добавляет элементы из очереди q.
Удаление элементов
val pop_last_opt : 'a t -> 'a option
pop_last_opt a удаляет и возвращает последний элемент a, или None если массив пуст.
val pop_last : 'a t -> 'a
pop_last a удаляет и возвращает последний элемент a.
-
Возбуждает
Not_foundпри пустом массиве.
val remove_last : 'a t -> unit
remove_last a удаляет последний элемент a, если таковой имеется. Она ничего не делает, если a пуст.
val truncate : 'a t -> int -> unit
truncate a n обрезает a до максимального числа n элементов.
Удаляются элементы, чей индекс больше или равен n. Она ничего не делает, если n >= length a.
truncate a n эквивалентно:
if n < 0 then invalid_argument "...";
while length a > n do
remove_last a
done
-
Возбуждает
Invalid_argumentеслиn < 0.
val clear : 'a t -> unit
clear a — это truncate a 0, она удаляет все элементы a.
Итерация
Функции итерации обрабатывают элементы динамического массива. Итерации по a вычисляются в порядке возрастания индексов: от элемента с индексом 0 до элемента с индексом length a - 1.
Это программистская ошибка изменять длину массива (добавляя или удаляя элементы) во время итерации по массиву. Любая функция итерации завершится с Invalid_argument если она обнаружит такое изменение длины.
val iter : ('a -> unit) -> 'a t -> unit
iter f a вызывает f для каждого элемента a.
val iteri : (int -> 'a -> unit) -> 'a t -> unit
iteri f a вызывает f i x для каждого x с индексом i в a.
val map : ('a -> 'b) -> 'a t -> 'b t
map f a — это новый массив элементов в форме f x для каждого элемента x a.
Например, если элементы a — это x0, x1, x2, то элементы b — это f x0, f x1, f x2.
val mapi : (int -> 'a -> 'b) -> 'a t -> 'b t
mapi f a — это новый массив элементов в форме f i x для каждого элемента x a с индексом i.
Например, если элементы a — это x0, x1, x2, то элементы b — это f 0 x0, f 1 x1, f 2 x2.
val fold_left : ('acc -> 'a -> 'acc) -> 'acc -> 'a t -> 'acc
fold_left f acc a складывает f по порядку, начиная с накопителя acc.
Например, если элементы a равны x0, x1, то fold f acc a равен
let acc = f acc x0 in
let acc = f acc x1 in
acc
val fold_right : ('a -> 'acc -> 'acc) -> 'a t -> 'acc -> 'acc
fold_right f a acc вычисляет f x0 (f x1 (... (f xn acc) ...)), где x0, x1, ..., xn — элементы a.
val exists : ('a -> bool) -> 'a t -> bool
exists f a равен true, если какой-либо элемент a удовлетворяет условию f.
Например, если элементы a равны x0, x1, x2, то exists f a равен f x0 || f x1 || f x2.
val for_all : ('a -> bool) -> 'a t -> bool
for_all f a равен true, если все элементы a удовлетворяют условию f. Это включает случай, когда a пустой.
Например, если элементы a равны x0, x1, то exists f a равен f x0 && f x1 && f x2.
val filter : ('a -> bool) -> 'a t -> 'a t
filter f a — новый массив, содержащий все элементы a, которые удовлетворяют условию f. Другими словами, это массив b, в котором для каждого элемента x в a по порядку x добавляется в b если f x равно true.
Например, filter (fun x -> x >= 0) a — это новый массив, содержащий все неотрицательные элементы a в порядке.
val filter_map : ('a -> 'b option) -> 'a t -> 'b t
filter_map f a — новый массив элементов y, такой, что f x равно Some y для элемента x из a.
Иными словами, это массив b, такой что для каждого элемента x из a в порядке:
- если
f x = Some y, тоyдобавляется вb, - если
f x = None, то элементы не добавляются вb.
Например, filter_map int_of_string_opt inputs возвращает новый массив целых чисел, считанных из строк в inputs, игнорируя строки, которые нельзя преобразовать в целые числа.
Преобразования в другие структуры данных
Примечание: функции of_* вызывают исключение Invalid_argument если длина должна вырасти за пределы Sys.max_array_length.
Функции to_*, кроме явно помеченных как «рекурсивные», итерируют по аргументу dynarray. В частности, это ошибка программирования, если длина dynarray изменяется во время их выполнения, и функции преобразования вызывают исключение Invalid_argument если они наблюдают такое изменение.
val of_array : 'a array -> 'a t
of_array arr возвращает динамический массив, соответствующий массиву фиксированной длины a. Выполняется за O(n) время путем создания копии.
val to_array : 'a t -> 'a array
to_array a возвращает массив фиксированной длины, соответствующий динамическому массиву a. Всегда выделяет новый массив и копирует элементы в него.
val of_list : 'a list -> 'a t
of_list l — это массив, содержащий элементы l в том же порядке.
val to_list : 'a t -> 'a list
to_list a — список с элементами, содержащимися в массиве a.
val of_seq : 'a Seq.t -> 'a t
of_seq seq — это массив, содержащий те же элементы, что и seq.
Он проходит по seq один раз и завершится только если seq конечен.
val to_seq : 'a t -> 'a Seq.t
to_seq a — последовательность элементов get a 0, get a 1... get a (length a - 1).
val to_seq_reentrant : 'a t -> 'a Seq.t
to_seq_reentrant a — это рекурсивная версия Dynarray.to_seq в том смысле, что к её элементам можно обращаться после изменения длины a.
Обращение к элементу i результата последовательности (что может произойти ноль, один или несколько раз) приведет к обращению к элементу i из a на момент обращения. Последовательность завершается, если у a меньше чем i элементов в этот момент.
val to_seq_rev : 'a t -> 'a Seq.t
to_seq_rev a — это последовательность элементов get a (l - 1), get a (l - 2)... get a 0, где l равно length a в момент вызова to_seq_rev.
val to_seq_rev_reentrant : 'a t -> 'a Seq.t
to_seq_rev_reentrant a — это рекурсивная версия Dynarray.to_seq_rev в том смысле, что к её элементам можно обращаться после изменения длины a.
Элементы, удалённые из массива к моменту обращения к ним в последовательности, пропускаются.
Расширенные темы производительности
Поддерживающий массив, ёмкость
Внутренне, динамический массив использует поддерживающий массив (массив фиксированной длины, предоставляемый модулем Array), длина которого больше или равна длине динамического массива. Мы определяем ёмкость динамического массива как длину его поддерживающего массива.
Ёмкость динамического массива важна в расширенных сценариях, когда необходимо рассуждать о производительности программ с динамическими массивами:
- Использование памяти динамическим массивом пропорционально его ёмкости, а не длине.
- Когда свободных ячеек в конце поддерживающего массива нет, добавление элементов требует выделения нового, большего поддерживающего массива.
Реализация использует стандартную стратегию экспоненциального перераспределения, которая гарантирует амортизированное постоянное время операции; в частности, общая ёмкость всех поддерживающих массивов, выделенных за время жизни динамического массива, в худшем случае пропорциональна общему числу добавленных элементов.
Другими словами, пользователям не нужно беспокоиться о ёмкости и перераспределении, и они получат приемлемое поведение по умолчанию. Однако в некоторых сценариях, чувствительных к производительности, приведенные ниже функции могут помочь контролировать использование памяти или гарантировать оптимальное число перераспределений.
val capacity : 'a t -> int
capacity a — это длина поддерживающего массива a.
val ensure_capacity : 'a t -> int -> unit
ensure_capacity a n гарантирует, что ёмкость a не меньше n.
-
Возвращает
Invalid_argumentесли запрошенная ёмкость находится вне диапазона0 .. Sys.max_array_length. Примером является повторная реализацияDynarray.of_arrayбез использованияDynarray.init:let of_array arr = let a = Dynarray.create () in Dynarray.ensure_capacity a (Array.length arr); Array.iter (fun v -> add_last a v) arrИспользованиеensure_capacityгарантирует, что произойдет не более одного перераспределения, а не, возможно, нескольких. Без этогоensure_capacityуказания число перераспределений будет логарифмическим относительно длиныarr, что создаст замедление, заметное при большойarr.
val ensure_extra_capacity : 'a t -> int -> unit
ensure_extra_capacity a n — это ensure_capacity a (length a + n), она гарантирует, что у a достаточно места для n дополнительных элементов.
-
Возвращает
Invalid_argumentесли общая запрошенная ёмкость находится вне диапазона0 .. Sys.max_array_length. Пример использования: реализацияDynarray.append_array:let append_array a arr = ensure_extra_capacity a (Array.length arr); Array.iter (fun v -> add_last a v) arr
val fit_capacity : 'a t -> unit
fit_capacity a перераспределяет поддерживающий массив при необходимости, чтобы итоговая ёмкость была ровно length a, без дополнительного пустого пространства в конце. Это может быть полезно для того, чтобы избежать потерь памяти в долгоживущем массиве.
Обратите внимание, что вызов fit_capacity нарушает амортизированные гарантии сложности, предоставляемые стратегией перераспределения по умолчанию. Повторный вызов для массива может иметь квадратичную сложность как во времени, так и в общем количестве выделенной памяти.
Если известно, что длина динамического массива достигла конечного значения, которое останется неизменным в будущем, достаточно вызвать to_array и сохранить полученный массив фиксированной длины. fit_capacity полезно, когда нужно сохранить динамический массив для возможных будущих изменений размера.
val set_capacity : 'a t -> int -> unit
set_capacity a n перераспределяет поддерживающий массив при необходимости, чтобы итоговая ёмкость была ровно n. В частности, все элементы с индексом n или выше удаляются.
Как и Dynarray.fit_capacity, эта функция нарушает амортизированные гарантии сложности, предоставляемые стратегией перераспределения. Повторный вызов для массива может иметь квадратичную сложность как во времени, так и в общем количестве выделенной памяти.
Это расширенная функция; в частности, следует предпочесть Dynarray.ensure_capacity для увеличения ёмкости, поскольку она сохраняет эти амортизированные гарантии.
-
Возвращает
Invalid_argumentеслиn < 0.
val reset : 'a t -> unit
reset a очищает a и заменяет его поддерживающий массив пустым массивом.
Это эквивалентно set_capacity a 0 или clear a; fit_capacity a.
Отсутствие утечек памяти: сохранение актуальности памяти
Значения, предоставленные пользователем и доступные из динамического массива a , — это ровно элементы в позициях 0 по length a - 1. В частности, никакие значения, предоставленные пользователем, не «утечка» путем присутствия в базовом массиве в позиции length a или позже.
Макет памяти dynarrays
В текущей реализации базовый массив 'a Dynarray.t не является 'a array, а чем-то с таким же представлением, как 'a option array или 'a ref array. Каждый элемент находится в «боксе», выделенном при первом добавлении элемента в массив — см. реализацию для получения более подробной информации.
Использование 'a array было бы деликатным, так как нет очевидного способа типично-корректного представления пустого места в конце базового массива — использование значений, предоставленных пользователем, либо усложнит API, либо нарушит гарантию «нет утечек» нет утечек. Ограничение сохранения безопасности памяти при несинхронизированном одновременном использовании делает это еще сложнее. Различные небезопасные способы сделать это обсуждались, но консенсуса по стандартной реализации пока нет.
В программе автоматического доказательства теорем, реалистичной и сильно зависящей от динамических массивов, мы измерили издержки этого дополнительного «боксов» максимум в 25%. Мы считаем, что издержки для большинства применений dynarray намного меньше, в многих случаях пренебрежимы, но вы все равно можете предпочесть использовать собственную специализированную реализацию для повышения производительности. (Если вам не нужна гарантия «нет утечек» нет утечек, вы также можете ускорить удаление элементов.)
Примеры кода
Минимальные кучи для изменяемых очередей с приоритетом
Мы можем использовать динамические массивы для реализации изменяемой очереди с приоритетом. Очередь с приоритетом предоставляет функцию добавления элементов и функцию извлечения минимального элемента — согласно некоторой функции сравнения.
(* We present our priority queues as a functor
parametrized on the comparison function. *)
module Heap (Elem : Map.OrderedType) : sig
type t
val create : unit -> t
val add : t -> Elem.t -> unit
val pop_min : t -> Elem.t option
end = struct
(* Our priority queues are implemented using the standard "min heap"
data structure, a dynamic array representing a binary tree. *)
type t = Elem.t Dynarray.t
let create = Dynarray.create
(* The node of index [i] has as children the nodes of index [2 * i + 1]
and [2 * i + 2] -- if they are valid indices in the dynarray. *)
let left_child i = 2 * i + 1
let right_child i = 2 * i + 2
let parent_node i = (i - 1) / 2
(* We use indexing operators for convenient notations. *)
let ( .!() ) = Dynarray.get
let ( .!()<- ) = Dynarray.set
(* Auxiliary functions to compare and swap two elements
in the dynamic array. *)
let order h i j =
Elem.compare h.!(i) h.!(j)
let swap h i j =
let v = h.!(i) in
h.!(i) <- h.!(j);
h.!(j) <- v
(* We say that a heap respects the "heap ordering" if the value of
each node is smaller than the value of its children. The
algorithm manipulates arrays that respect the heap algorithm,
except for one node whose value may be too small or too large.
The auxiliary functions [heap_up] and [heap_down] take
such a misplaced value, and move it "up" (respectively: "down")
the tree by permuting it with its parent value (respectively:
a child value) until the heap ordering is restored. *)
let rec heap_up h i =
if i = 0 then () else
let parent = parent_node i in
if order h i parent < 0 then
(swap h i parent; heap_up h parent)
and heap_down h ~len i =
let left, right = left_child i, right_child i in
if left >= len then () (* no child, stop *) else
let smallest =
if right >= len then left (* no right child *) else
if order h left right < 0 then left else right
in
if order h i smallest > 0 then
(swap h i smallest; heap_down h ~len smallest)
let add h s =
let i = Dynarray.length h in
Dynarray.add_last h s;
heap_up h i
let pop_min h =
if Dynarray.is_empty h then None
else begin
(* Standard trick: swap the 'best' value at index 0
with the last value of the array. *)
let last = Dynarray.length h - 1 in
swap h 0 last;
(* At this point [pop_last] returns the 'best' value,
and leaves a heap with one misplaced element at position 0. *)
let best = Dynarray.pop_last h in
(* Restore the heap ordering -- does nothing if the heap is empty. *)
heap_down h ~len:last 0;
Some best
end
end
Производственный код, на котором был основан этот пример, включает логику освобождения базового массива при опустошении кучи только в том случае, если емкость выше определенного порога. Это можно сделать, вызвав следующую функцию из pop:
let shrink h =
if Dynarray.length h = 0 && Dynarray.capacity h > 1 lsl 18 then
Dynarray.reset h
Функтор Heap можно использовать для реализации функции сортировки, добавив все элементы в очередь с приоритетом, а затем извлекая их в нужном порядке.
let heap_sort (type a) cmp li = let module Heap = Heap(struct type t = a let compare = cmp end) in let heap = Heap.create () in List.iter (Heap.add heap) li; List.map (fun _ -> Heap.pop_min heap |> Option.get) li
© 1995-2024 INRIA.
https://ocaml.org/manual/5.2/api/Dynarray.html