Spec-Zone.ru › OCaml

Модуль 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API