Professional Documents
Culture Documents
Levi Pearson
1
Intro
OUTLINE
1. Basics of Functional Data Structures
Immutability
Persistent vs. Ephemeral
Node Structure
Data Sharing
2. Analysis Techniques
Amortization Overview
Amortization with Persistence
3. Design Techniques
4. Real-World Examples
Zippers
2-3 Finger Trees
Hash Array Mapped Tries
3
MOTIVATION
Basics
PERSISTENCE
VALUE OF PERSISTENCE
NODE-BASED STRUCTURE
Functional data structures share structural characteristics:
Examples:
data List a = Nil
| Cons a (List a)
data Tree a = Leaf a
| Branch (Tree a) (Tree a)
DATA SHARING
10
Analysis
11
Amortization
12
13
ALGORITHM COMPARISON
14
WORST CASE
15
AVERAGE CASE
16
AMORTIZED
17
WHEN TO AMORTIZE?
A worst-case analysis is necessary when you have hard latency
bounds, e.g. real-time requirements.
Average or amortized analysis can give a better sense of expected
throughput of an algorithm.
Amortized analysis is useful when most operations are cheap, but
occasionally an expensive one must be performed.
Examples:
Array doubling
Rehashing
Tree balancing
the
foo
foo
foo
teetering tree
tr1 -- causes a rebalance: expensive
tr1 -- causes another rebalance
tr1 -- causes yet another rebalance
How to account for that? You cant spend your savings more than
once.
19
Functional Amortization
20
the
foo
bar
baz
teetering tree
tr1 -- creates a rebalancing thunk: cheap
tr2 -- forces rebalancing: expensive
tr2 -- sees previous rebalancing
21
22
This has a small space and complexity overhead, but bounds the
latency.
23
24
Implementation Patterns
25
Lazy Rebuilding
26
QUEUES, TAKE 1
The essence of the Queue:
QUEUE IMPLEMENTATION
28
QUEUE ANALYSIS
Most operations are trivial:
empty: O (1)
insert: O (1)
length: O (1)
29
30
NOT SO FAST
let
let
let
let
q1
q2
q3
q4
=
=
=
=
foldr insert
remove q1 -remove q1 -remove q1 --
empty "Abracadabra"
Causes a reverse
Causes the same reverse
And again...
31
32
Forcing a tail:
33
FULL IMPLEMENTATION
module Queue2 where
import Prelude hiding (length)
data Queue a = Q { left :: [a], leftLen :: !Int
, right :: [a], rightLen :: !Int
} deriving (Show, Eq)
empty :: Queue a
empty = Q [] 0 [] 0
length :: Queue a -> Int
length (Q _ ll _ rl) = ll + rl
insert :: a -> Queue a -> Queue a
insert e (Q l ll r rl) = makeQ (Q l ll (e : r) (rl + 1))
remove :: Queue a -> (a, Queue a)
remove (Q l ll r rl) = (head l, makeQ (Q (tail l) (ll-1) r rl))
makeQ :: Queue a -> Queue a -- Preserve invariant: |R| <= |L|
makeQ q@(Q l ll r rl)
| rl <= ll = q
| otherwise = Q (rot l r []) (ll + rl) [] 0
rot :: [a] -> [a] -> [a] -> [a]
rot [] r a = head r : a
rot l r a = head l : rot (tail l) (tail r) (head r : a)
34
Numerical Representations
35
ARITHMETIC
36
b i wi
i=0
REDUNDANCY
1011
1201
122
39
40
DENSE EXAMPLE
41
SPARSE EXAMPLE
Store a list of di wi values, so Zero digits wont appear
type SparseNat = [Int] -- increasing list of powers of 2
carry w []
= [w]
carry w ws@(w' : rest)
| w < w'
= w : ws
| otherwise = carry (2 * w) rest
borrow w ws@(w' : rest)
| w < w'
= rest
| otherwise = w : borrow (2 * w) ws
inc ws = carry 1 ws
dec ws = borrow 1 ws
add ws [] = ws
add [] ws = ws
add m@(w1 : ws1) n@(w2 : ws2)
| w1 < w2
= w1 : add ws1 n
| w2 < w1
= w2 : add m ws2
| otherwise = carry (2 * w1) (add ws1 ws2)
42
For example:
size 1
size 8
size 64
43
A complete binary leaf tree has all Leaf nodes at the same rank.
data BLT a = Leaf a
| Fork (BLT a) (BLT a)
44
:: DenseNat
[]
(Zero : ds)
(One : ds)
-> DenseNat
= [One]
= One : ds
= Zero : inc ds -- carry
:: DenseNat
[One]
(One : ds)
(Zero : ds)
-> DenseNat
= []
= Zero : ds
= One : dec ds -- borrow
47
PROPERTIES OF RANDLIST
48
i=0
= 14 + 15 + 63
= 92
49
wi is 2i+1 1
(
)
1 + 2 2i+1 1 = 2i+2 1
This means that when the lowest non-0 digit is 2, the inc
operation:
resets the 2 to 0
increments the next digit from 0 to 1 or 1 to 2
51
Non-Uniform Structure
52
Constraint violations:
zeroless binary
redundant binary
skew binary
segmented binary
higher bases
bonacci numbers
factorials - random permutation algorithm
56
Real-World Structures
57
Zippers
58
WHAT IS A ZIPPER?
Imagine you have a large, complex data structure
A zipper is a complementary structure that tracks a navigation
path through the primary structure.
It contains:
Operators on a zipper:
Finally, we need to combine the path with the focus point, which
will just be a subtree of the tree we are traversing:
data Location a = Loc (Tree a) (Path a)
To build a zipper, we just combine the root of the tree with the
Top path constructor in a Location
60
61
62
63
64
65
66
67
Key features:
type Key
= Word
type Bitmap = Word
data HAMT a = Empty
| BitmapIndexed !Bitmap !(Vector (HAMT a))
| Leaf !Key a
| Full !(Vector (HAMT a))
68
Conclusion
71
Numerical Representations
Lazy Amortization
Non-Uniform Type Structure