packages feed

ddc-code-0.4.3.1: tetra/base/Data/Map.ds

module Data.Map
export  
{       map_empty;  map_singleton;
        map_null;   map_size;
        map_lookup; map_member;
        map_insert; map_insertWithKey;
        map_foldr;  map_foldrWithKey; map_foldlWithKey;
        map_fromList;
        map_toList; map_toAscList; map_toDescList;
        show_map;
}
import Data.Numeric.Nat
import Data.Numeric.Bool
import Data.Text
import Data.Maybe
import Data.List
import Data.Tuple
import Class.Ord
import Class.Show
where


-- | A map from keys @k@ to values @a@.
data Map (k a: Data) where
        Bin : Size -> k -> a -> Map k a -> Map k a -> Map k a
        Tip : Map k a


type Size = Nat


show_map (show_k: Show k) (show_a: Show a): Show (Map k a)
 = Show sh
 where  
        sh (Bin s k a l r)
         = parens $ "Bin"
                %% show show_nat s
                %% show show_k   k
                %% show show_a   a
                %% show (show_map show_k show_a) l
                %% show (show_map show_k show_a) r

        sh Tip
         = "Tip"


-- Construction -----------------------------------------------------------------------------------
-- | O(1). The empty map.
map_empty : Map k a
 = Tip


-- | O(1). A map with a single element.
map_singleton (k: k) (x: a) : Map k a
 = Bin 1 k x Tip Tip


-- Query ------------------------------------------------------------------------------------------
-- | /O(1)/. Is the map empty?
--
-- > Data.Map.null (empty)           == True
-- > Data.Map.null (singleton 1 'a') == False
map_null (mp: Map k a): Bool
 = case mp of
        Tip             -> True
        Bin _ _ _ _ _   -> False


-- | /O(1)/. The number of elements in the map.
--
-- > size empty                                   == 0
-- > size (singleton 1 'a')                       == 1
-- > size (fromList([(1,'a'), (2,'c'), (3,'b')])) == 3
map_size (mp: Map k a): Size
 = case mp of
        Tip             -> 0
        Bin sz _ _ _ _  -> sz
  

-- | /O(log n)/. Lookup the value at a key in the map.
--
-- The function will return the corresponding value as @('Just' value)@,
-- or 'Nothing' if the key isn't in the map.
map_lookup ((Ord compare): Ord k) (kx: k) (mp: Map k a): Maybe a
 = go kx mp
 where  
        go _ Tip 
         = Nothing [a]

        go k (Bin _ kx x l r)
         = case compare k kx of
                LT -> go k l
                GT -> go k r
                EQ -> Just x


-- | /O(log n)/. Is the key a member of the map?
--
-- > member 5 (fromList [(5,'a'), (3,'b')]) == True
-- > member 1 (fromList [(5,'a'), (3,'b')]) == False
--
map_member ((Ord compare): Ord k) (kx: k) (mp: Map k a): Bool
 = go kx mp
 where  
        go _ Tip 
         = False

        go k (Bin _ kx _ l r)
         = case compare k kx of
                LT -> go k l
                GT -> go k r
                EQ -> True


-- Insertion --------------------------------------------------------------------------------------
-- | /O(log n)/. Insert a new key and value in the map.
--   If the key is already present in the map, the associated value is
--   replaced with the supplied value. 'insert' is equivalent to
--   @'insertWith' 'const'@.
--
-- > insert 5 'x' (fromList [(5,'a'), (3,'b')]) == fromList [(3, 'b'), (5, 'x')]
-- > insert 7 'x' (fromList [(5,'a'), (3,'b')]) == fromList [(3, 'b'), (5, 'a'), (7, 'x')]
-- > insert 5 'x' empty                         == singleton 5 'x'
--
map_insert
        ((Ord compare): Ord k) 
        (kx0: k) (x0: a) (mp: Map k a): Map k a
 = go kx0 x0 mp
 where
        go kx x Tip 
         = map_singleton kx x

        go kx x (Bin sz ky y l r)
         = case compare kx ky of
                LT -> map_balance ky y (go kx x l) r
                GT -> map_balance ky y l (go kx x r)
                EQ -> Bin sz kx x l r


-- | /O(log n)/. Insert with a function, combining new value and old value.
--   @'insertWith' f key value mp@
--   will insert the pair (key, value) into @mp@ if key does
--   not exist in the map. If the key does exist, the function will
--   insert the pair @(key, f new_value old_value)@.
--
map_insertWith
        (ord: Ord k)
        (f: a -> a -> a)
        (kx: k) (x: a) (mp: Map k a)
        : Map k a
 = map_insertWithKey ord
        (\_ x' y' -> f x' y')
        kx x mp


-- | /O(log n)/. Insert with a function, combining key, new value and old value.
--   @'insertWithKey' f key value mp@
--   will insert the pair (key, value) into @mp@ if key does
--   not exist in the map. If the key does exist, the function will
--   insert the pair @(key,f key new_value old_value)@.
--   Note that the key passed to f is the same key passed to 'insertWithKey'.
--
map_insertWithKey
        ((Ord compare): Ord k)
        (f: k -> a -> a -> a)
        (kx: k) (x: a) (mp: Map k a)
        : Map k a
 = go kx x mp
 where
        go kx x Tip
         = map_singleton kx x

        go kx x (Bin sy ky y l r)
         = case compare kx ky of
                LT -> map_balance ky y (go kx x l) r
                GT -> map_balance ky y l (go kx x r)
                EQ -> Bin sy kx (f kx x y) l r


-- Folds ------------------------------------------------------------------------------------------
-- | /O(n)/. Fold the values in the map using the given right-associative
--   binary operator.
--
map_foldr (f: a -> b -> b) (z: b) (mp: Map k a): b
 = go z mp
 where
        go z' Tip             = z'
        go z' (Bin _ _ x l r) = go (f x (go z' r)) l


-- | /O(n)/. Fold the keys and values in the map using the given right-associative
--   binary operator..
--
map_foldrWithKey (f: k -> a -> b -> b) (z: b) (mp: Map k a): b
 = go z mp
 where
        go z' Tip              = z'
        go z' (Bin _ kx x l r) = go (f kx x (go z' r)) l


-- | /O(n)/. Fold the keys and values in the map using the given left-associative
-- binary operator.
map_foldlWithKey (f: a -> k -> b -> a) (z: a) (mp: Map k b): a
 = go z mp
 where  go z' Tip              = z'
        go z' (Bin _ kx x l r) = go (f (go z' l) kx x) r


-- Conversion -------------------------------------------------------------------------------------
map_fromList 
        (ord: Ord k)
        (xx: List (Tup2 k a))
        : Map k a
 = foldl (λ  mp tp
          -> case tp of
                 T2 kx x -> map_insert ord kx x mp)
         map_empty xx


-- | /O(n)/. Convert the map to a list of key\/value pairs. 
map_toList (mp: Map k a): List (Tup2 k a)
        = map_toAscList mp


-- | /O(n)/. Convert the map to a list of key\/value pairs where the
--   keys are in ascending order. 
map_toAscList (mp: Map k a): List (Tup2 k a)
        = map_foldrWithKey (λk x xs -> Cons (T2 k x) xs) Nil mp


-- | /O(n)/. Convert the map to a list of key\/value pairs where the
--   keys are in descending order.
map_toDescList (mp: Map k a): List (Tup2 k a)
        = map_foldlWithKey (λxs k x -> Cons (T2 k x) xs) Nil mp


---------------------------------------------------------------------------------------------------
-- [balance l x r] balances two trees with value x.
--  The sizes of the trees should balance after decreasing the
--  size of one of them. (a rotation).
--
--  [delta] is the maximal relative difference between the sizes of
--          two trees, it corresponds with the [w] in Adams' paper.
--
--  [ratio] is the ratio between an outer and inner sibling of the
--          heavier subtree in an unbalanced setting. It determines
--          whether a double or single rotation should be performed
--          to restore balance. It is corresponds with the inverse
--          of $\alpha$ in Adam's article.
--
--  Note that according to the Adam's paper:
--  - [delta] should be larger than 4.646 with a [ratio] of 2.
--  - [delta] should be larger than 3.745 with a [ratio] of 1.534.
--
--  But the Adam's paper is erroneous:
--  - It can be proved that for delta=2 and delta>=5 there does
--    not exist any ratio that would work.
--  - Delta=4.5 and ratio=2 does not work.
--
--  That leaves two reasonable variants, delta=3 and delta=4,
--  both with ratio=2.
--
--  - A lower [delta] leads to a more 'perfectly' balanced tree.
--  - A higher [delta] performs less rebalancing.
--
--  In the benchmarks, delta=3 is faster on insert operations,
--  and delta=4 has slightly better deletes. As the insert speedup
--  is larger, we currently use delta=3.
--
--  NOTE: The Haskell implementation from the containers package
--  contains an unfolded version of balance to optimise pattern 
--  matching, but there is no point using that until we have the
--  same sort of pattern matching compiler optimisations as GHC.
-- 
delta   = 2
ratio   = 5


map_balance (k: k) (x: a) (l: Map k a) (r: Map k a): Map k a
 = let  sizeL = map_size l in
   let  sizeR = map_size r in
   let  sizeX = sizeL + sizeR + 1 
   in   match
         | sizeL + sizeR <= 1    = Bin sizeX k x l r
         | sizeR > delta*sizeL   = rotateL k x l r
         | sizeL > delta*sizeR   = rotateR k x l r
         | otherwise             = Bin sizeX k x l r


rotateL (k: k) (x: a)
        (l: Map k a) (r@(Bin _ _ _ ly ry): Map k a)
        : Map k a
 | map_size ly < ratio*map_size ry = singleL k x l r
 | otherwise                       = doubleL k x l r


rotateR (k: k) (x: a)
        (l@(Bin _ _ _ ly ry): Map k a) (r: Map k a)
        : Map k a
 | map_size ry < ratio*map_size ly = singleR k x l r
 | otherwise                       = doubleR k x l r


singleL (k1: k) (x1: a) (t1: Map k a) (tR: Map k a): Map k a
 | Bin _ k2 x2 t2 t3 <- tR
 = bin k2 x2 (bin k1 x1 t1 t2) t3

singleR (k1: k) (x1: a) (tL: Map k a) (t3: Map k a): Map k a
 | Bin _ k2 x2 t1 t2 <- tL
 = bin k2 x2 t1 (bin k1 x1 t2 t3)

doubleL (k1: k) (x1: a) (t1: Map k a) (tR: Map k a): Map k a
 | Bin _ k2 x2 (Bin _ k3 x3 t2 t3) t4 <- tR
 = bin k3 x3 (bin k1 x1 t1 t2) (bin k2 x2 t3 t4)

doubleR (k1: k) (x1: a) (tL: Map k a) (t4: Map k a): Map k a
 | Bin _ k2 x2 t1 (Bin _ k3 x3 t2 t3) <- tL
 = bin k3 x3 (bin k2 x2 t1 t2) (bin k1 x1 t3 t4)


-- | The bin constructor maintains the size of the tree
bin (k: k) (x: a) (l: Map k a) (r: Map k a): Map k a
 = Bin (map_size l + map_size r + 1) k x l r