Создание двусвязного списка из списка в OCaml
Мне часто говорят, что с помощью модуля Lazy
в OCaml можно делать все, что вы можете сделать на ленивом языке, таком как Haskell. Чтобы проверить это утверждение, я пытаюсь написать функцию, которая преобразует обычный список в статический дважды связанный список в ocaml.
type 'a dlist = Dnil | Dnode of 'a dlist * 'a * 'a dlist
Учитывая этот тип, я могу вручную создать несколько статических дважды связанных списков:
let rec l1 = Dnode (Dnil,1,l2)
and l2 = Dnode (l1,2,l3)
and l3 = Dnode (l2,3,Dnil)
но я хотел бы написать функцию типа 'a list -> 'a dlist
, которая при условии, что любой список создает статический двусвязный список в OCaml. Например, для данного [1;2;3]
он должен вывести что-то эквивалентное l1
выше.
Алгоритм довольно простой для записи в Haskell:
data DList a = Dnil | Dnode (DList a) a (DList a)
toDList :: [a] -> DList a
toDList l = go Dnil l
where
go _ [] = Dnil
go h (x:xs) = let r = Dnode h x (go r xs) in r
но мне не удалось выяснить, куда поместить вызовы на Lazy
, чтобы получить это для компиляции в OCaml.
Ответы
Ответ 1
Если вы создадите свой связанный список в порядке справа налево (как и для обычных списков), то левый элемент каждого node будет создан только после того, как будет построен сам node. Вы должны представить это, сделав левый элемент ленивым, что означает, что "это значение будет построено позже":
type 'a dlist =
| Dnil
| Dnode of 'a dlist Lazy.t * 'a * 'a dlist
После этого создайте каждый node как ленивое значение, используя рекурсивное определение, которое передает ленивый (все еще неконструированный) node вызов функции, который строит следующий node (так, чтобы он имел доступ к предыдущий node). Это на самом деле проще, чем выглядит:
let dlist_of_list list =
let rec aux prev = function
| [] -> Dnil
| h :: t -> let rec node = lazy (Dnode (prev, h, aux node t)) in
Lazy.force node
in
aux (Lazy.lazy_from_val Dnil) list
Ответ 2
Вы можете построить только циклическую неизменную строгую структуру данных формы, которая была определена во время компиляции. Я не буду определять это или формально, но интуитивно говоря, как только структура данных будет создана, его форма не изменится (потому что она неизменна). Таким образом, вы не можете добавить к циклу. И если вы создаете какой-либо элемент цикла, вам нужно одновременно создать все остальные элементы цикла, потому что у вас не может быть никакого висячего указателя.
Ocaml может делать то, что может сделать Haskell, но вам нужно задействовать модуль Lazy
! В отличие от Haskell, структуры данных ML строги, если не указано иное. У ленивой структуры данных есть фрагменты типа 'a Lazy.t
. (ML-ввод более точен, чем Haskell по этой конкретной проблеме.) Ленивые структуры данных позволяют создавать циклы с помощью предустановленных указателей (привязанные значения которых автоматически создаются при первом разыменовании указателя).
type 'a lazy_dlist_value =
| Dnil
| Dnode of 'a lazy_dlist_value * 'a * 'a lazy_dlist_value
and 'a lazy_dlist = 'a lazy_dlist_value Lazy.t
Другим распространенным способом создания циклических структур данных является использование изменяемых узлов. (На самом деле, твердые сторонники строгого программирования могут видеть ленивые структуры данных как особый случай изменчивых структур данных, которые не слишком сильно нарушают ссылочную прозрачность.)
type 'a mutable_dlist_value =
| Dnil
| Dnode of 'a mutable_dlist_value * 'a * 'a mutable_dlist_value
and 'a mutable_dlist = 'a mutable_dlist_value ref
Структуры циклических данных в основном полезны, когда они включают по меньшей мере один изменяемый компонент, одну функцию (закрытие) или иногда модули. Но компилятору нечего было бы принуждать к этому: циклические строгие неизменные структуры данных первого порядка - это особый случай, который иногда может быть полезным.
Ответ 3
type 'a dlist = Dnil | Dnode of 'a dlist Lazy.t * 'a * 'a dlist Lazy.t
let rec of_list list = match list with
[] -> Dnil
| x :: [] ->
let rec single () = Dnode (lazy (single ()), x, lazy (single ()))
in single ()
| x :: y -> Dnode (
lazy (
of_list (match List.rev list with
[] | _ :: [] -> assert false
| x :: y -> x :: List.rev y
)
),
x,
lazy (
of_list (match list with
[] | _ :: [] -> assert false
| x :: y -> y @ x :: []
)
)
)
let middle dlist = match dlist with
Dnil -> raise (Failure "middle")
| Dnode (_, x, _) -> x
let left dlist = match dlist with
Dnil -> raise (Failure "left")
| Dnode (x, _, _) -> Lazy.force x
let right dlist = match dlist with
Dnil -> raise (Failure "right")
| Dnode (_, _, x) -> Lazy.force x