diff options
| author | Rose Hogenson <rhogenson@posteo.net> | 2023-02-10 15:05:19 -0800 |
|---|---|---|
| committer | Rose Hogenson <rhogenson@posteo.net> | 2023-02-10 15:05:19 -0800 |
| commit | a85ca78cbbcc5c1c81471b1d4e91d952efe4a2c3 (patch) | |
| tree | c8c9dbebaf954dc7e60a326247c5a542166f0cf9 /map.sml | |
| parent | 5d0463456bc39dd39ec31680ecd09b6320da1935 (diff) | |
| download | sml-a85ca78cbbcc5c1c81471b1d4e91d952efe4a2c3.tar.zst | |
Re-implement map.
I stole Haskell's map, and I read a paper.
Diffstat (limited to 'map.sml')
| -rw-r--r-- | map.sml | 277 |
1 files changed, 168 insertions, 109 deletions
@@ -1,13 +1,23 @@ -(* map is a red-black tree implementing an immutable key-value store. *) +(* map is a size-balanced binary tree implementing an immutable key-value store. + + * Stolen with love from Haskell's Data.Map. *) signature MAP = sig type key type 'a map val empty : 'a map - val insert : 'a map -> key * 'a -> 'a map - val lookup : 'a map -> key -> 'a option - val delete : 'a map -> key -> 'a map + val null : 'a map -> bool + val size : 'a map -> int + val insert : key -> 'a -> 'a map -> 'a map + val lookup : key -> 'a map -> 'a option + val delete : key -> 'a map -> 'a map + val union : 'a map -> 'a map -> 'a map + val intersect : 'a map -> 'a map -> 'a map + val difference : 'a map -> 'b map -> 'a map + val fromList : (key * 'a) list -> 'a map + val toList : 'a map -> (key * 'a) list + val lookupMin : 'a map -> (key * 'a) option end functor Map (type k @@ -15,121 +25,170 @@ functor Map (type k struct type key = k - datatype color = Red | Black - datatype 'a node = Node of color * (key * 'a) * 'a node option * 'a node option - type 'a map = 'a node option - - val empty = NONE + datatype 'a map = Tip + | Bin of int * key * 'a * 'a map * 'a map - fun rebalance (Node (Black, g, SOME (Node (Red, p, SOME (Node (Red, n, nLeft, nRight)), pRight)), u)) : 'a node = - (* b - / - r - / - r *) - Node (Red, p, SOME (Node (Black, n, nLeft, nRight)), - SOME (Node (Black, g, pRight, u))) - | rebalance (Node (Black, g, u, SOME (Node (Red, p, pLeft, SOME (Node (Red, n, nLeft, nRight)))))) = - (* b - \ - r - \ - r *) - Node (Red, p, SOME (Node (Black, g, u, pLeft)), - SOME (Node (Black, n, nLeft, nRight))) - | rebalance (Node (Black, g, u, SOME (Node (Red, p, SOME (Node (Red, n, nLeft, nRight)), pRight)))) = - (* b - \ - r - / - r *) - Node (Red, n, SOME (Node (Black, g, u, nLeft)), - SOME (Node (Black, p, nRight, pRight))) - | rebalance (Node (Black, g, SOME (Node (Red, p, pLeft, SOME (Node (Red, n, nLeft, nRight)))), u)) = - (* b - / - r - \ - r *) - Node (Red, n, SOME (Node (Black, p, pLeft, nLeft)), - SOME (Node (Black, g, nRight, u))) - | rebalance n = n - - fun insert m (kv as (k, _)) = - let - fun insertNode NONE = Node (Red, kv, NONE, NONE) - | insertNode (SOME (Node (color, nodeKV as (nodeKey, _), left, right))) = - case cmp (k, nodeKey) of - LESS => rebalance (Node (color, nodeKV, SOME (insertNode left), right)) - | EQUAL => Node (color, kv, left, right) - | GREATER => rebalance (Node (color, nodeKV, left, SOME (insertNode right))) - val Node (_, nodeKV, left, right) = insertNode m - in - SOME (Node (Black, nodeKV, left, right)) + val empty : 'a map = Tip + + fun null Tip = true + | null _ = false + + fun size Tip = 0 + | size (Bin (s, _, _, _, _)) = s + + fun lookup (k : key) (m : 'a map) : 'a option = + let fun go Tip = NONE + | go (Bin (_, kx, x, l, r)) = + case cmp (k, kx) of + LESS => go l + | GREATER => go r + | EQUAL => SOME x + in go m + end + + fun singleton (k : key) (v : 'a) : 'a map = Bin (1, k, v, Tip, Tip) + + fun size Tip = 0 + | size (Bin (sz, _, _, _, _)) = sz + + (* The bin constructor maintains the size of the tree. *) + fun bin k x l r = Bin (size l + size r + 1, k, x, l, r) + + (* https://hackage.haskell.org/package/containers-0.4.0.0/docs/src/Data-Map.html#delta *) + val delta = 4 + val ratio = 2 + + fun singleL k1 x1 t1 (Bin (_, k2, x2, t2, t3)) = bin k2 x2 (bin k1 x1 t1 t2) t3 + | singleL _ _ _ Tip = raise Fail "singleL Tip" + + fun singleR k1 x1 (Bin (_, k2, x2, t1, t2)) t3 = bin k2 x2 t1 (bin k1 x1 t2 t3) + | singleR _ _ Tip _ = raise Fail "singleR Tip" + + fun doubleL k1 x1 t1 (Bin (_, k2, x2, Bin (_, k3, x3, t2, t3), t4)) = bin k3 x3 (bin k1 x1 t1 t2) (bin k2 x2 t3 t4) + | doubleL _ _ _ _ = raise Fail "doubleL" + + fun doubleR k1 x1 (Bin (_, k2, x2, t1, Bin (_, k3, x3, t2, t3))) t4 = bin k3 x3 (bin k2 x2 t1 t2) (bin k1 x1 t3 t4) + | doubleR _ _ _ _ = raise Fail "doubleR" + + fun rotateL k x l (r as Bin (_, _, _, ly, ry)) = + if size ly < ratio*size ry then singleL k x l r + else doubleL k x l r + | rotateL _ _ _ Tip = raise Fail "rotateL Tip" + + fun rotateR k x (l as Bin (_, _, _, ly, ry)) r = + if size ry < ratio*size ly then singleR k x l r + else doubleR k x l r + | rotateR _ _ Tip _ = raise Fail "rotateR Tip" + + fun balance k x l r = + let + val sizeL = size l + val sizeR = size r + in + if sizeL + sizeR <= 1 then bin k x l r + else if sizeR >= delta*sizeL then rotateL k x l r + else if sizeL >= delta*sizeR then rotateR k x l r + else bin k x l r + end + + fun insertMax kx x t = + case t of + Tip => singleton kx x + | Bin (_, ky, y, l, r) => + balance ky y l (insertMax kx x r) + + fun insertMin kx x t = + case t of + Tip => singleton kx x + | Bin (_, ky, y, l, r) => + balance ky y (insertMin kx x l) r + + fun join kx x Tip r = insertMin kx x r + | join kx x l Tip = insertMax kx x l + | join kx x (l as Bin (sizeL, ky, y, ly, ry)) (r as Bin (sizeR, kz, z, lz, rz)) = + if delta*sizeL <= sizeR then balance kz z (join kx x l lz) rz + else if delta*sizeR <= sizeL then balance ky y ly (join kx x ry r) + else bin kx x l r + + fun split (t : 'a map) (k : key) = + case t of + Tip => (Tip, false, Tip) + | Bin (_, km, m, l, r) => + case cmp (k, km) of + EQUAL => (l, true, r) + | LESS => + let val (ll, b, lr) = split l k + in (ll, b, join km m lr r) + end + | GREATER => + let val (rl, b, rr) = split r k + in (join km m l rl, b, rr) + end + + fun splitLast (Bin (_, kx, x, l, Tip)) = (l, (kx, x)) + | splitLast (Bin (_, kx, x, l, r)) = + let val (t', k') = splitLast r + in (join kx x l t', k') + end + | splitLast Tip = raise Fail "splitLast Tip" + + fun join2 Tip tr = tr + | join2 tl tr = + let val (tl', (kx, x)) = splitLast tl + in join kx x tl' tr end - fun lookup m k = - let fun loop NONE = NONE - | loop (SOME (Node (_, (nodeKey, nodeVal), left, right))) = - case cmp (k, nodeKey) of - LESS => loop left - | EQUAL => SOME nodeVal - | GREATER => loop right - in loop m + fun delete k m = + let val (tl, _, tr) = split m k + in join2 tl tr + end + + fun union (t1 : 'a map) (t2 : 'a map) : 'a map = + case (t1, t2) of + (Tip, _) => t2 + | (_, Tip) => t1 + | (_, Bin (_, k2, v2, l2, r2)) => + let + val (l1, _, r1) = split t1 k2 + val tl = union l1 l2 + val tr = union r1 r2 + in join k2 v2 tl tr end - fun sub1 (SOME (Node (Black, x, a, b))) = Node (Red, x, a, b) - | sub1 _ = raise Fail "invariance violation" + fun intersect Tip _ = Tip + | intersect _ Tip = Tip + | intersect t1 (Bin (_, k2, v2, l2, r2)) = + let + val (l1, b, r1) = split t1 k2 + val tl = intersect l1 l2 + val tr = intersect r1 r2 + in + if b + then join k2 v2 tl tr + else join2 tl tr + end - (* Thanks to https://github.com/sweirich/dth/blob/master/examples/red-black/RedBlack.lhs *) - fun delete t k = + fun difference Tip _ = Tip + | difference t1 Tip = t1 + | difference t1 (Bin (_, k2, v2, l2, r2)) = let - fun del NONE = NONE - | del (SOME (Node (_, (nodeKV as (nodeKey, _)), left, right))) = - case cmp (k, nodeKey) of - LESS => SOME (delLeft nodeKV left right) - | EQUAL => merge left right - | GREATER => SOME (delRight nodeKV left right) + val (l1, _, r1) = split t1 k2 + val tl = difference l1 l2 + val tr = difference r1 r2 + in join2 tl tr + end - and delLeft nodeKV (a as SOME (Node (Black, _, _, _))) b = balLeft nodeKV (del a) b - | delLeft nodeKV a b = Node (Red, nodeKV, del a, b) + fun insert (k : key) (v : 'a) (m : 'a map) : 'a map = union m (singleton k v) - and balLeft x (SOME (Node (Red, y, a, b))) c = Node (Red, x, SOME (Node (Black, y, a, b)), c) - | balLeft x bl (SOME (Node (Black, y, a, b))) = - rebalance (Node (Black, x, bl, SOME (Node (Red, y, a, b)))) - | balLeft x bl (SOME (Node (Red, z, SOME (Node (Black, y, a, b)), c))) = - Node (Red, y, SOME (Node (Black, x, bl, a)), SOME (rebalance (Node (Black, z, b, SOME (sub1 c))))) - | balLeft _ _ _ = raise Fail "unreachable" + fun fromList l = foldl (fn ((kx, x), acc) => insert kx x acc) empty l - and delRight nodeKV a (b as SOME (Node (Black, _, _, _))) = balRight nodeKV a (del b) - | delRight nodeKV a b = Node (Red, nodeKV, a, del b) + fun toList' Tip acc = acc + | toList' (Bin (_, k, v, l, r)) acc = + toList' l ((k, v) :: toList' r acc) - and balRight x a (SOME (Node (Red, y, b, c))) = Node (Red, x, a, SOME (Node (Black, y, b, c))) - | balRight y (SOME (Node (Black, x, a, b))) bl = rebalance (Node (Black, y, SOME (Node (Red, x, a, b)), bl)) - | balRight z (SOME (Node (Red, x, a, SOME (Node (Black, y, b, c))))) bl = - Node (Red, y, SOME (rebalance (Node (Black, x, SOME (sub1 a), b))), - SOME (Node (Black, z, c, bl))) - | balRight _ _ _ = raise Fail "unreachable" + fun toList m = toList' m [] - and merge NONE x = x - | merge x NONE = x - | merge (SOME (Node (Red, x, a, b))) (SOME (Node (Red, y, c, d))) = - (case merge b c of - SOME (Node (Red, z, b', c')) => - SOME (Node (Red, z, SOME (Node (Red, x, a, b')), - SOME (Node (Red, y, c', d)))) - | bc => SOME (Node (Red, x, a, SOME (Node (Red, y, bc, d))))) - | merge (SOME (Node (Black, x, a, b))) (SOME (Node (Black, y, c, d))) = - (case merge b c of - SOME (Node (Red, z, b', c')) => - SOME (Node (Red, z, SOME (Node (Black, x, a, b')), - SOME (Node (Black, y, c', d)))) - | bc => SOME (balLeft x a (SOME (Node (Black, y, bc, d))))) - | merge a (SOME (Node (Red, x, b, c))) = SOME (Node (Red, x, merge a b, c)) - | merge (SOME (Node (Red, x, a, b))) c = SOME (Node (Red, x, a, merge b c)) - in - case del t of - NONE => NONE - | SOME (Node (_, kv, left, right)) => SOME (Node (Black, kv, left, right)) - end + fun lookupMin Tip = NONE + | lookupMin (Bin (_, k, v, Tip, _)) = SOME (k, v) + | lookupMin (Bin (_, _, _, l, _)) = lookupMin l end |
