简体   繁体   中英

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. 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")

  • A finger tree, which is a mind-blowing data structure invented by Ralf Hinze and 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

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:

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?" As Norman Ramsey notes: if you need multi-directional traversal, then zippers are easier; 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:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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