5. Hashes

Introduction


-- this choose for association the traditionnal assoc list representation
age = [ ("Nat",24)
        ,("Jules",25)
        ,("Josh",17)]
-- if you preprend the list with fromList, then you can decide what representaion to choose by typing age
age':: Assoc String Int
age' = fromList [ ("Nat",24)
                  ,("Jules",25)
                  ,("Josh",17)]

age1:: Assoc String Int
age1 = empty
age2 = age1.insert ("Nat",24)
age3 = age2.insert ("Jules",25)
age4 = age3.insert ("Josh",17)

food_color :: Assoc String String
food_color =  fromList [ "Apple"  ==> "red"
                        ,"Banana" ==> "yellow"
                        ,"Lemon"  ==> "yellow"
                        ,"Carrot" ==> "orange"
                        ]

-- In haskell, you cannot avoid the double quoting while manipulatin hashes

Adding an Element to a Hash

food_color' = food_color.insert ("Raspberry" ==> "pink")
mfood_color = putStrLn ("Know foods: "^(unwords (food_color.keys)))

Testing for the Presence of a Key in a Hash

-- does hash have a value for key ?

mhash = ["Banana", "Martini"].mapM(\name -> 
        putStrLn (name^" is a "^(food_color.has_key name ? ("food","drink")))
                                  )

Deleting from a Hash

food_color2 = food_color.delete_key "Banana"

Traversing a Hash


mhash2 = food_color.each (\(food,color) ->
         putStrLn (food^" is "^color)
         )
mhash3 = food_color.each_key(\food -> 
         putStrLn (food^" is "^(food_color!food))
         )

-- IMO too (as gc), this demonstrates that OO style is by far more readable
mhash4 = food_color.keys.sort.each(\food ->
         putStrLn (food^" is "^(food_color!food))
         )
---------------------------------------
#!/usr/bin/runhugs -98
module Main where

import Prelude hiding (($),(^),(.),(!!),map,take,lookup,drop,splitAt,reverse,filter,takeWhile,dropWhile,null,foldl,length)
import Common
-- TODO, very slow, try with ghc

-- countfrom - count number of messages from each sender
main = do lines <- (<>)
          lines.foldl(\from l -> 
                     if l =~ "^From: " 
                     then let [name] = l.match "^From: (.*)" in from.update name (+1)
                     else from
                     ) (empty::AssocDefault String Int)
                .toList.sortBy (\(_,a) (_,b) -> b <=> a)
                .each (\(a,b) -> putStrLn (b^": "^a))
---------------------------------------
                              
                           

Printing a Hash

-- You may use the not built-in 'show' (poor ruby)
mhash5 = putStrLn (age4.show)
         
-- Or do it the Cookbook way:
mhash5' = age4.each (\(k,v) -> putStrLn (k^" => "^v))

-- Sorted by keys
mhash6 = age4.toList.sortBy (\a b -> (a.fst) <=> (b.fst)).each(\(k,v) -> putStrLn (k^" => "^v))
-- Sorted by values
mhash7 = age4.toList.sortBy (\a b -> (a.snd) <=> (b.snd)).each(\(k,v) -> putStrLn (k^" => "^v))

Retrieving from a Hash in Insertion Order

Hashes with Multiple Values Per Key

mttys = do who <- exec "who"
           who.foldl(\ttys l -> 
                    let (user:tty:_)= l.words in
                    ttys.update tty (insert user)
                    ) (empty::AssocDefault String (SetList String))
                .toList.sortBy (\(a,_) (b,_) -> b <=> a)
                .each (\(a,b) -> putStrLn (a^": "^commify_series (b.toList)))

Inverting a Hash

surname:: Assoc String String
surname = fromList ["Mickey" ==> "Mantle", "Babe"  ==> "Ruth"]
mhash8 = surname.keyss "Mantle".head.putStrLn

invert_surname:: Assoc String String
invert_surname = surname.toList.map (\(a,b) -> (b,a)).fromList

---------------------------------------
#!/usr/bin/runhugs -98
module Main where

import Prelude hiding (($),(^),(.),(!!),map,take,lookup,drop,splitAt,reverse,filter,takeWhile,dropWhile,null,foldl,length)
import Common

color :: Assoc String String
color = fromList [
                 "Apple"  ==> "red",
                 "Banana" ==> "yellow",
                 "Lemon"  ==> "yellow",
                 "Carrot" ==> "orange"
                 ]


-- foodfind - find match for food or color
main = do args <- argv
          usage "foodfind food_or_color" `when` (args.null)
          let given = args.head in
              do putStrLn (given^" is a good with color "^(color!given)) 
                              `when` (color.has_key   given)
                 putStrLn (given^" is a color with food "^(color.keyss given.head)) 
                              `when` (color.has_value given)
---------------------------------------

Sorting a Hash

-- Sorted by keys
mhash9  = age4.toList.sortBy (\a b -> (a.fst) <=> (b.fst)).each (\(k,v) -> 
          putStrLn (k^" is "^v))
-- Sorted by values
mhash10 = age4.toList.sortBy (\a b -> (a.snd) <=> (b.snd)).each (\(k,v) -> 
          putStrLn (k^" is "^v))

-- Sorted by length of values
mhash11 = food_color.toList.sortBy (\a b -> (a.snd.length) <=> (b.snd.length)).each (\(k,v) -> 
          putStrLn (k^" is "^v))

Merging Hashes

drink_color:: Assoc String String
drink_color = fromList ["Galliano" ==> "yellow", "Mai Tai" ==> "blue"]

-- in haskell no clone, we do implictly always clone :)
ingested_color = drink_color `union` food_color

substance_color :: IO (Assoc String Int)
substance_color = [food_color, drink_color]
                  .foldM (\a i -> 
                          i.keys.foldM (\a k -> 
                                        if a.has_key k 
                                        then putStrLn ("Warning: "^k^" seen twice") >> (return a)
                                        else return (a.insert (k,1))
                                        ) a
                          ) empty

Finding Common or Different Keys in Two Hashes

common = food_color.keys * drink_color.keys

this_not_that = food_color.keys - drink_color.keys

Hashing References

-- no problem here, haskell handles any kind of object for key-ing

-- TODO 5.13 AFAIK, not possible in Ruby

Presizing a Hash

Finding the Most Common Anything

count:: AssocDefault String Int
count = ["toto","toto","toto","tata","tata","tutu"].foldl (\a s -> a.update s (+1)) empty

--@PLEAC@@_5.15
father:: Assoc String String
father = fromList ["Cain"      ==> "Adam",
                   "Abel"      ==> "Adam",
                   "Seth"      ==> "Adam",
                   "Enoch"     ==> "Cain",
                   "Irad"      ==> "Enoch",
                   "Mehujael"  ==> "Irad",
                   "Methusael" ==> "Mehujael",
                   "Lamech"    ==> "Methusael",
                   "Jabal"     ==> "Lamech",
                   "Jubal"     ==> "Lamech",
                   "Tubalcain" ==> "Lamech",
                   "Enos"      ==> "Seth"
                   ]


children :: AssocDefault String (SetList String)
children = father.foldl (\a (k,v) -> a.update v (insert k)) empty

-- TODO    

Representing Relationships Between Data

Program: dutree