elm/libraries/Dict.elm

354 lines
11 KiB
Elm
Raw Normal View History

2013-12-19 00:03:20 +00:00
module Dict (empty,singleton,insert,update
2012-10-21 11:52:46 +00:00
,lookup,findWithDefault
,remove,member
,foldl,foldr,map
,union,intersect,diff
,keys,values
,toList,fromList
) where
2013-09-10 06:07:49 +00:00
{-| A dictionary mapping unique keys to values. The keys can be any comparable
2013-09-10 02:12:27 +00:00
type. This includes `Int`, `Float`, `Time`, `Char`, `String`, and tuples or
lists of comparable types.
Insert, remove, and query operations all take *O(log n)* time.
# Build
2013-12-19 00:03:20 +00:00
@docs empty, singleton, insert, update, remove
2013-09-10 02:12:27 +00:00
# Query
@docs member, lookup, findWithDefault
# Combine
@docs union, intersect, diff
# Lists
@docs keys, values, toList, fromList
# Transform
@docs map, foldl, foldr
-}
import Basics (..)
import Maybe (..)
2013-07-26 14:38:11 +00:00
import Native.Error
import List
import Native.Utils
2013-09-17 06:39:36 +00:00
-- BBlack and NBlack should only be used during the deletion
-- algorithm. Any other occurrence is a bug and should fail an assert.
data NColor = Red
| Black
-- ^ Double Black, counts as 2 blacks for the invariant
| BBlack
-- ^ Negative Black, counts as -1 blacks for the invariant
| NBlack
showNColor : NColor -> String
showNColor c = case c of
Red -> "Red"
Black -> "Black"
BBlack -> "BBlack"
NBlack -> "NBlack"
data LeafColor = LBlack
-- ^ Double Black, counts as 2
| LBBlack
showLColor : LeafColor -> String
showLColor c = case c of
LBlack -> "LBlack"
LBBlack -> "LBBlack"
2013-09-17 06:39:36 +00:00
data Dict k v = RBNode NColor k v (Dict k v) (Dict k v)
| RBEmpty LeafColor
2012-10-10 21:37:42 +00:00
2013-09-10 02:12:27 +00:00
{-| Create an empty dictionary. -}
2013-07-26 14:38:11 +00:00
empty : Dict comparable v
2013-09-17 06:39:36 +00:00
empty = RBEmpty LBlack
2012-10-10 21:37:42 +00:00
min : Dict k v -> (k,v)
2012-10-10 21:37:42 +00:00
min t =
case t of
2013-09-17 06:39:36 +00:00
RBNode _ k v (RBEmpty LBlack) _ -> (k,v)
RBNode _ _ _ l _ -> min l
2013-09-17 06:39:36 +00:00
RBEmpty LBlack -> Native.Error.raise "(min Empty) is not defined"
2012-10-10 21:37:42 +00:00
2013-09-17 06:39:36 +00:00
max : Dict k v -> (k, v)
2012-10-10 21:37:42 +00:00
max t =
case t of
2013-09-17 06:39:36 +00:00
RBNode _ k v _ (RBEmpty _) -> (k,v)
RBNode _ _ _ _ r -> max r
RBEmpty _ -> Native.Error.raise "(max Empty) is not defined"
2012-10-10 21:37:42 +00:00
2013-09-10 02:12:27 +00:00
{-| Lookup the value associated with a key. -}
2013-07-26 14:38:11 +00:00
lookup : comparable -> Dict comparable v -> Maybe v
2012-10-10 21:37:42 +00:00
lookup k t =
case t of
2013-09-17 06:39:36 +00:00
RBEmpty LBlack -> Nothing
RBNode _ k' v l r ->
case Native.Utils.compare k k' of
LT -> lookup k l
2013-07-26 14:38:11 +00:00
EQ -> Just v
GT -> lookup k r
2012-10-10 21:37:42 +00:00
2013-09-10 02:12:27 +00:00
{-| Find the value associated with a key. If the key is not found,
return the default value. -}
2013-07-26 14:38:11 +00:00
findWithDefault : v -> comparable -> Dict comparable v -> v
findWithDefault base k t =
case t of
2013-09-17 06:39:36 +00:00
RBEmpty LBlack -> base
RBNode _ k' v l r ->
case Native.Utils.compare k k' of
LT -> findWithDefault base k l
EQ -> v
GT -> findWithDefault base k r
2013-09-10 02:12:27 +00:00
{-| Determine if a key is in a dictionary. -}
2013-07-26 14:38:11 +00:00
member : comparable -> Dict comparable v -> Bool
-- Does t contain k?
2013-07-26 14:38:11 +00:00
member k t = isJust <| lookup k t
2012-10-10 21:37:42 +00:00
ensureBlackRoot : Dict k v -> Dict k v
ensureBlackRoot t =
2012-10-10 21:37:42 +00:00
case t of
RBNode Red k v l r -> RBNode Black k v l r
2013-09-17 06:39:36 +00:00
RBNode Black _ _ _ _ -> t
RBEmpty LBlack -> t
2013-09-10 02:12:27 +00:00
{-| Insert a key-value pair into a dictionary. Replaces value when there is
a collision. -}
2013-07-26 14:38:11 +00:00
insert : comparable -> v -> Dict comparable v -> Dict comparable v
2013-12-19 10:13:10 +00:00
insert k v t = let u _ = Just v in
update k u t
2013-12-19 00:03:20 +00:00
2013-12-19 10:13:10 +00:00
{-| Remove a key-value pair from a dictionary. If the key is not found,
no changes are made. -}
remove : comparable -> Dict comparable v -> Dict comparable v
remove k t = let u _ = Nothing in
update k u t
data Flag = Insert | Remove | Same
showFlag : Flag -> String
showFlag f = case f of
Insert -> "Insert"
Remove -> "Remove"
Same -> "Same"
2012-10-10 21:37:42 +00:00
2013-12-19 10:13:10 +00:00
{-| Update the value of a dictionary for a specific key with a given function. -}
update : comparable -> (Maybe v -> Maybe v) -> Dict comparable v -> Dict comparable v
update k u t =
let up t = case t of
RBEmpty LBlack -> case u Nothing of
Nothing -> (Same, empty)
Just v -> (Insert, RBNode Red k v empty empty)
RBNode c k' v l r -> case Native.Utils.compare k k' of
EQ -> case u (Just v) of
Nothing -> (Remove, rem c l r)
2013-12-19 10:13:10 +00:00
Just v' -> (Same, RBNode c k' v' l r)
LT -> let (fl, l') = up l in
case fl of
Same -> (Same, RBNode c k' v l' r)
Insert -> (Insert, balance c k' v l' r)
2013-12-19 10:13:10 +00:00
Remove -> (Remove, bubble c k' v l' r)
GT -> let (fl, r') = up r in
case fl of
Same -> (Same, RBNode c k' v l r')
Insert -> (Insert, balance c k' v l r')
2013-12-19 10:13:10 +00:00
Remove -> (Remove, bubble c k' v l r')
(fl, t') = up t
in case fl of
Same -> t'
Insert -> ensureBlackRoot t'
Remove -> blacken t'
2013-09-10 02:12:27 +00:00
{-| Create a dictionary with one key-value pair. -}
2013-07-26 14:38:11 +00:00
singleton : comparable -> v -> Dict comparable v
2013-09-17 06:39:36 +00:00
singleton k v = insert k v (RBEmpty LBlack)
isBBlack : Dict k v -> Bool
isBBlack t = case t of
RBNode BBlack _ _ _ _ -> True
2013-09-17 06:39:36 +00:00
RBEmpty LBBlack -> True
_ -> False
moreBlack : NColor -> NColor
moreBlack c = case c of
Black -> BBlack
Red -> Black
NBlack -> Red
BBlack -> Native.Error.raise "Can't make a double black node more black!"
2013-09-17 06:39:36 +00:00
lessBlack : NColor -> NColor
lessBlack c = case c of
BBlack -> Black
Black -> Red
Red -> NBlack
NBlack -> Native.Error.raise "Can't make a negative black node less black!"
2013-09-17 06:39:36 +00:00
lessBlackTree : Dict k v -> Dict k v
lessBlackTree t = case t of
RBNode c k v l r -> RBNode (lessBlack c) k v l r
RBEmpty LBBlack -> RBEmpty LBlack
reportRemBug : String -> NColor -> String -> String -> a
reportRemBug msg c lgot rgot =
Native.Error.raise <| List.concat [
"Internal red-black tree invariant violated, expected ",
msg,
"and got",
showNColor c,
" ",
lgot,
" ",
rgot,
"\nPlease report this bug to https://github.com/evancz/Elm/issues"
]
2013-09-17 06:39:36 +00:00
2013-09-18 08:48:39 +00:00
-- Remove the top node from the tree, may leave behind BBlacks
rem : NColor -> Dict k v -> Dict k v -> Dict k v
rem c l r = case (l, r) of
((RBEmpty _), (RBEmpty _)) -> case c of
Red -> RBEmpty LBlack
2013-09-18 08:48:39 +00:00
Black -> RBEmpty LBBlack
((RBEmpty cl), (RBNode cr k' v' l' r')) ->
case (c, cl, cr) of
(Black, LBlack, Red) -> RBNode Black k' v' l' r'
_ -> reportRemBug "Black, LBlack, Red" c (showLColor cl) (showNColor cr)
((RBNode cl k' v' l' r'), (RBEmpty cr)) ->
case (c, cl, cr) of
(Black, Red, LBlack) -> RBNode Black k' v' l' r'
_ -> reportRemBug "Black, Red, LBlack" c (showNColor cl) (showLColor cr)
2013-09-18 08:48:39 +00:00
-- l and r are both RBNodes
((RBNode cl kl vl ll rl), (RBNode cr kr vr lr rr)) ->
let l = RBNode cl kl vl ll rl
r = RBNode cr kr vr lr rr
(k, v) = max l
l' = remove_max cl kl vl ll rl
2013-09-18 08:48:39 +00:00
in bubble c k v l' r
2013-09-17 06:39:36 +00:00
-- Kills a BBlack or moves it upward, may leave behind NBlack
bubble : NColor -> k -> v -> Dict k v -> Dict k v -> Dict k v
bubble c k v l r = if isBBlack l || isBBlack r
then balance (moreBlack c) k v (lessBlackTree l) (lessBlackTree r)
else RBNode c k v l r
2013-09-17 06:39:36 +00:00
-- Removes rightmost node, may leave root as BBlack
remove_max : NColor -> k -> v -> Dict k v -> Dict k v -> Dict k v
remove_max c k v l r = case r of
RBEmpty _ -> rem c l r
RBNode cr kr vr lr rr
-> bubble c k v l (remove_max cr kr vr lr rr)
2013-09-17 06:39:36 +00:00
-- generalized tree balancing act
balance : NColor -> k -> v -> Dict k v -> Dict k v -> Dict k v
balance c k v l r =
balance_node (RBNode c k v l r)
2013-09-17 06:39:36 +00:00
blackish : Dict k v -> Bool
blackish t = case t of
RBNode c _ _ _ _ -> c == Black || c == BBlack
RBEmpty _ -> True
2013-09-17 06:39:36 +00:00
balance_node : Dict k v -> Dict k v
balance_node t =
let assemble col xk xv yk yv zk zv a b c d =
RBNode (lessBlack col) yk yv (RBNode Black xk xv a b) (RBNode Black zk zv c d)
in
if blackish t
then case t of
RBNode col zk zv (RBNode Red yk yv (RBNode Red xk xv a b) c) d ->
assemble col xk xv yk yv zk zv a b c d
RBNode col zk zv (RBNode Red xk xv a (RBNode Red yk yv b c)) d ->
assemble col xk xv yk yv zk zv a b c d
RBNode col xk xv a (RBNode Red zk zv (RBNode Red yk yv b c) d) ->
assemble col xk xv yk yv zk zv a b c d
RBNode col xk xv a (RBNode Red yk yv b (RBNode Red zk zv c d)) ->
assemble col xk xv yk yv zk zv a b c d
RBNode BBlack xk xv a (RBNode NBlack zk zv (RBNode Black yk yv b c) d) ->
case d of
(RBNode Black _ _ _ _) ->
RBNode Black yk yv (RBNode Black xk xv a b) (balance Black zk zv c (redden d))
_ -> t
RBNode BBlack zk zv (RBNode NBlack xk xv a (RBNode Black yk yv b c)) d ->
case a of
(RBNode Black _ _ _ _) ->
RBNode Black yk yv (balance Black xk xv (redden a) b) (RBNode Black zk zv c d)
_ -> t
_ -> t
else t
-- make the top node black
blacken : Dict k v -> Dict k v
blacken t = case t of
RBEmpty _ -> RBEmpty LBlack
RBNode _ k v l r -> RBNode Black k v l r
-- make the top node red
redden : Dict k v -> Dict k v
redden t = case t of
RBEmpty _ -> Native.Error.raise "can't make a Leaf red"
RBNode _ k v l r -> RBNode Red k v l r
2013-09-10 02:12:27 +00:00
{-| Apply a function to all values in a dictionary. -}
2013-07-26 14:38:11 +00:00
map : (a -> b) -> Dict comparable a -> Dict comparable b
map f t =
case t of
2013-09-17 06:39:36 +00:00
RBEmpty LBlack -> RBEmpty LBlack
RBNode c k v l r -> RBNode c k (f v) (map f l) (map f r)
2012-10-10 21:37:42 +00:00
2013-09-10 02:12:27 +00:00
{-| Fold over the key-value pairs in a dictionary, in order from lowest
key to highest key. -}
2013-07-26 14:38:11 +00:00
foldl : (comparable -> v -> b -> b) -> b -> Dict comparable v -> b
foldl f acc t =
case t of
2013-09-17 06:39:36 +00:00
RBEmpty LBlack -> acc
RBNode _ k v l r -> foldl f (f k v (foldl f acc l)) r
2013-09-10 02:12:27 +00:00
{-| Fold over the key-value pairs in a dictionary, in order from highest
key to lowest key. -}
2013-07-26 14:38:11 +00:00
foldr : (comparable -> v -> b -> b) -> b -> Dict comparable v -> b
foldr f acc t =
2012-10-10 21:37:42 +00:00
case t of
2013-09-17 06:39:36 +00:00
RBEmpty LBlack -> acc
RBNode _ k v l r -> foldr f (f k v (foldr f acc r)) l
2013-09-10 02:12:27 +00:00
{-| Combine two dictionaries. If there is a collision, preference is given
to the first dictionary. -}
2013-07-26 14:38:11 +00:00
union : Dict comparable v -> Dict comparable v -> Dict comparable v
union t1 t2 = foldl insert t2 t1
2013-09-10 02:12:27 +00:00
{-| Keep a key-value pair when its key appears in the second dictionary.
Preference is given to values in the first dictionary. -}
2013-07-26 14:38:11 +00:00
intersect : Dict comparable v -> Dict comparable v -> Dict comparable v
intersect t1 t2 =
let combine k v t = if k `member` t2 then insert k v t else t
in foldl combine empty t1
2013-09-10 02:12:27 +00:00
{-| Keep a key-value pair when its key does not appear in the second dictionary.
Preference is given to the first dictionary. -}
2013-07-26 14:38:11 +00:00
diff : Dict comparable v -> Dict comparable v -> Dict comparable v
diff t1 t2 = foldl (\k v t -> remove k t) t1 t2
2013-09-10 02:12:27 +00:00
{-| Get all of the keys in a dictionary. -}
2013-07-26 14:38:11 +00:00
keys : Dict comparable v -> [comparable]
keys t = foldr (\k v acc -> k :: acc) [] t
2013-09-10 02:12:27 +00:00
{-| Get all of the values in a dictionary. -}
2013-07-26 14:38:11 +00:00
values : Dict comparable v -> [v]
values t = foldr (\k v acc -> v :: acc) [] t
2013-09-10 02:12:27 +00:00
{-| Convert a dictionary into an association list of key-value pairs. -}
2013-07-26 14:38:11 +00:00
toList : Dict comparable v -> [(comparable,v)]
toList t = foldr (\k v acc -> (k,v) :: acc) [] t
2013-09-10 02:12:27 +00:00
{-| Convert an association list into a dictionary. -}
2013-07-26 14:38:11 +00:00
fromList : [(comparable,v)] -> Dict comparable v
fromList assocs = List.foldl (\(k,v) d -> insert k v d) empty assocs