[Haskell-cafe] Doubly-linked zipper list w/ insert implementation

Justin Bailey jgbailey at gmail.com
Wed Nov 7 10:52:28 EST 2007


The other day I decided to implement a ring buffer with a current
element (i.e. a doubly-linked zipper list). In order to allow inserts
(and, in the future, deletes and updates), I have a special sentinel
element called "Join" in the structure. When inserting, I find the
join first, insert and then rebuild the buffer using circular
programming techniques. This also allows the buffer to be converted
back to a list. The current element can be changed by rotating right
or left, which never fails. Rotating n positions takes n steps.

I'm posting it here for comments and feedback. How could the structure
be smarter? Would storing a unique ID with each element make more
sense? Any comments on the space behavior under insert and rotates? I
wanted to "maximize" sharing. Thanks in advance.

Justin

p.s. The original motivation for writing this was to model cellular
automata. The CA world is "circular", so that got me thinking about a
structure that made connecting the ends easy to do.

-- cut here ---

module Ring (Ring, create, insert, current, rotR, rotL, toListL, toListR)
-- Thanks to Keith Wansbrough  for his posting describing a doubly-linked list
-- in Haskell for inspiration here.
-- 
-- http://groups.google.com/group/comp.lang.functional/msg/8c65fdd16f7e91e1
where

import Data.List (foldl')
-- For testing
import Test.QuickCheck
import Control.Monad (replicateM_)
import System.Random (randomRIO)
import System.Environment (getArgs)

data Ring a = Ring (Ring a) a (Ring a) | Join (Ring a) (Ring a)

instance (Show a) => Show (Ring a) where
  show r = "{" ++ show' (findLeftOfJoin r)
    where
      show' (Join _ _) = "}"
      show' (Ring l v _)
        | isJoin l = show v ++ show' l
        | otherwise = show v ++ "," ++ show' l

create v =
  let me = Ring join v join
      join = Join me me
  in me

insert r a =
    let (left, right) = insert' left right start a
        start = findLeftOfJoin r
    in left
  where
    insert' left right (Join _ _) val =
      let last = Ring join val right
          join = Join left last
      in (last, join)
    insert' left right (Ring l v _) val =
      let this = Ring newL val right
          (newL, newR) = insert' left this l v
      in (this, newR)

fromList [] = error "Can't create empty ring"
fromList ls =
    let (left, right) = fromList' left right ls
    in left
  where
    -- compute this ring, given left and right pointers. Return
    -- left and right pointers for this segment
    fromList' left right [] =
      let join = Join left right
      in (join, right)
    fromList' left right (x:xs) =
      let this = Ring l x right
          (l, r) = fromList' left this xs
      in (this, r)

toList = toListL

-- View of list from left hand side
toListL = toList' . findLeftOfJoin
  where
    toList' (Join _ _) = []
    toList' (Ring l v _) = v : toList' l

toListR = toList' . findRightOfJoin
  where
    toList' (Join _ _) = []
    toList' (Ring _ v r) = v : toList' r

current (Ring r v l) = v
current _ = error "Join is never current (curr)"

rotR r amt
  | amt > 0 = goRight r amt
  | amt < 0 = goLeft r (negate amt)
  | otherwise = r

rotL r amt
  | amt > 0 = goLeft r amt
  | amt < 0 = goRight r (negate amt)
  | otherwise = r

goRight r 0 = r
goRight (Ring _ _ r@(Ring _ _ _)) amt = goRight r (amt - 1)
goRight (Ring _ _ (Join _ r)) amt = goRight r (amt - 1)
goRight (Join _ _) _ = error "Join is never current (goRight)"

goLeft r 0 = r
goLeft (Ring l@(Ring _ _ _) _ _) amt = goLeft l (amt - 1)
goLeft (Ring (Join l _) _ _) amt = goLeft l (amt - 1)
goLeft (Join _ _) _ = error "Join is never current (goLeft)"

isRing (Ring _ _ _) = True
isRing _ = False

isJoin (Join _ _) = True
isJoin _ = False

findLeftOfJoin (Join l _) = l
findLeftOfJoin (Ring l _ _) = findLeftOfJoin l

findRightOfJoin (Join _ r) = r
findRightOfJoin (Ring _ _ r) = findRightOfJoin r


More information about the Haskell-Cafe mailing list