I'm playing around with the idea of a compact stack—one whose space requirements approach that of an array as its size increases. A candidate structure:
data Stack a
= Empty
| Zero (Stack a)
| One !(SmallArray a) (Stack a)
| Two !(SmallArray a) !(SmallArray a) (Stack a)
-- Invariant: the array size at depth `n` is `2^n`.
push :: a -> Stack a -> Stack a
push = pushA . pure
pushA :: SmallArray a -> Stack a -> Stack a
pushA sa Empty = One sa Empty
pushA sa (Zero more) = One sa more
pushA sa1 (One sa2 more) = Two sa1 sa2 more
pushA sa1 (Two sa2 sa3 more) = One sa1 (pushA (sa2 <> sa3) more)
pop :: Stack a -> Maybe (a, Stack a)
pop stk = do
(sa, stk') <- popA stk
hd <- indexSmallArrayM sa 0
Just (hd, stk')
popA :: Stack a -> Maybe (SmallArray a, Stack a)
popA Empty = Nothing
popA (Zero more) = do
(sa, more') <- popA more
let !(sa1, sa2) = -- split sa in two
Just (sa1, One sa2 more')
popA (One sa more) = Just (sa, Zero more)
popA (Two sa1 sa2 more) = Just (sa1, One sa2 more)
Some numerical experimentation suggests that I can get an O(log n) average cost per operation for a sequence of n pushes. But is it possible to analyze this structure as having O(log n) cost per push or pop? Or if not, can this be done for a similar structure? I haven't been able to find an appropriate debit invariant. The tricky case seems to be a sequence of Two nodes followed by a One node, but I may just be approaching this all wrong.