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

时间:2009-12-04 00:59:06

标签: data-structures haskell functional-programming linked-list

如何使用纯函数式语言编写双向链表?也就是说,像Haskell这样的东西,你不在Monad,所以你没有变异。可能吗? (单链表显然很容易)。

4 个答案:

答案 0 :(得分:61)

在纯函数式语言中,双向链表并不那么有趣。双向链表的想法是能够抓住节点并朝任一方向前进,或者能够拼接到列表的中间。在纯函数语言中,您可能最好使用这两种数据结构中的一种:

  • 一个单独链接的列表,中间有一个指针,您可以从左侧或右侧(Huet的“Zipper”变体)

  • 手指树,是Ralf Hinze和Ross Paterson发明的令人兴奋的数据结构。

我是拉链的忠实粉丝;它在很多情况下都很有用。

答案 1 :(得分:20)

有很多方法。

如果你不想在构建它之后改变双向链表,你可以依靠懒惰来“打结”。

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

如果你想要一个可变的双向链表,你需要以某种方式伪造引用 - 或者使用真正的引用 - 这是Oleg Kiseylov提出并在这里实现的技巧:

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

有趣的是,请注意前者从根本上依赖懒惰来取得成功。你最终需要突变或懒惰来打结。

答案 2 :(得分:10)

我会重申musicfan的问题:“你到底需要做什么?”正如Norman Ramsey所说:如果你需要多向遍历,那么拉链就更容易了;如果你需要快速拼接,手指树很好用。

但是,只是看它看起来如何......

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

答案 3 :(得分:2)

在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

对于双重链表,我想可以做类似的事情。但是,在打字时,你必须依赖懒惰和记录是友好的结构。快速而肮脏的循环双向链表:

  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