简体   繁体   English

纯函数式编程语言中的双重链接列表

[英]Doubly Linked List in a Purely Functional Programming Language

How does one go about doing doubly linked lists in a pure functional language? 如何用纯函数式语言编写双向链表? That is, something like Haskell where you're not in a Monad so you don't have mutation. 也就是说,像Haskell这样的东西,你不在Monad,所以你没有变异。 Is it possible? 可能吗? (Singly linked list is obviously pretty easy). (单链表很明显很简单)。

In a pure functional language, a doubly-linked list is not that interesting. 在纯函数式语言中,双向链表并不那么有趣。 The idea of a doubly linked list is to be able to grab a node and go in either direction, or to be able to splice into the middle of a list. 双向链表的想法是能够抓住节点并朝任一方向前进,或者能够拼接到列表的中间。 In a pure functionaly language you probably are better off with one of these two data structures: 在纯函数式语言中,您最好使用以下两种数据结构之一:

  • A singly linked list with a pointer in the middle, from which you can go either left or right (a variant of Huet's "Zipper") 一个单独链接的列表,中间有一个指针,您可以从左侧或右侧(Huet的“Zipper”的变体)

  • A finger tree, which is a mind-blowing data structure invented by Ralf Hinze and Ross Paterson. 手指树,是由Ralf Hinze和Ross Paterson发明的令人兴奋的数据结构。

I'm a big fan of the zipper; 我是拉链的忠实粉丝; it's useful in a lot of situations. 它在很多情况下都很有用。

There are a number of approaches. 有很多方法。

If you don't want to mutate the doubly-linked list once you have constructed it you can just 'tie the knot' by relying on laziness. 如果你不想在构建它之后改变双向链表,你就可以依靠懒惰来“打结”。

http://www.haskell.org/haskellwiki/Tying_the_Knot http://www.haskell.org/haskellwiki/Tying_the_Knot

If you want a mutable doubly-linked list you need to fake references somehow -- or use real ones -- a la the trick proposed by Oleg Kiseylov and implemented here: 如果你想要一个可变的双链表,你需要以某种方式伪造引用 - 或者使用真正的引用 - 这是Oleg Kiseylov提出的技巧并在这里实现:

http://hackage.haskell.org/packages/archive/liboleg/2009.9.1/doc/html/Data-FDList.html http://hackage.haskell.org/packages/archive/liboleg/2009.9.1/doc/html/Data-FDList.html

Interestingly, note that the former relies fundamentally upon laziness to succeed. 有趣的是,请注意前者从根本上依赖懒惰来取得成功。 You ultimately need mutation or laziness to tie the knot. 你最终需要突变或懒惰来打结。

I would reiterate musicfan's question: "what exactly do you need this for?" 我会重申musicfan的问题:“你到底需要什么呢?” As Norman Ramsey notes: if you need multi-directional traversal, then zippers are easier; 正如Norman Ramsey所说:如果你需要多向遍历,那么拉链就更容易了; if you need fast splicing, finger trees work well. 如果你需要快速拼接,手指树很好用。

But, just to see how it looks... 但是,只是看它看起来如何......

import Control.Arrow
import Data.List

data LNode a = LNode { here :: a, prev :: LList a, next :: LList a }
type LList a = Maybe (LNode a)

toList :: LList a -> [a]
toList = unfoldr $ fmap $ here &&& next

fromList :: [a] -> LList a
fromList l = head nodes where
    nodes = scanr ((.) Just . uncurry LNode) Nothing $ zip l $ Nothing : nodes

append :: LList a -> LList a -> LList a
append = join Nothing where
    join k (Just a) b = a' where
        a' = Just $ a { prev = k, next = join a' (next a) b }
    join k _ (Just b) = b' where
        b' = Just $ b { prev = k, next = join b' Nothing (next b) }
    join _ _ _ = Nothing

In OCaml, for circular simply linked list you can always do something like that: 在OCaml中,对于循环简单链接列表,您可以始终执行以下操作:

type t = { a : t Lazy.t }

let cycle n =
  let rec start = {a = lazy (aux n) }
  and aux = function
    | 0 -> start
    | n -> { a = lazy (aux (n-1))}
  in start

For doubly linked lists, I imagine it's possible to do something similar. 对于双链表,我想可以做类似的事情。 But you have to rely on laziness and on records being friendly structures when it comes to typing. 但是,在打字时,你必须依赖懒惰和记录是友好的结构。 Quick and dirty cyclic doubly linked list: 快速而肮脏的循环双向链表:

  type 'a t = { data : 'a; before : 'a t Lazy.t; after : 'a t Lazy.t }

  let of_list l =
    match l with [] -> assert false | hd::tl ->
    let rec start = { data = hd; before = last; after = next }
    and couple = lazy (aux (lazy start) hd) 
    and next = lazy (Lazy.force (fst (Lazy.force couple)))
    and last = lazy (Lazy.force (snd (Lazy.force couple)))
    and aux before = function
    | [] -> (lazy start), before
    | hd::tl -> let rec current = lazy { data = hd; before = before; after = after }
                   and couple = lazy (aux current tl) 
                   and after = lazy (Lazy.force (fst (Lazy.force couple)))
                   and last = lazy (Lazy.force (snd (Lazy.force couple))) in
                   current, last
    in start

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM