Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Updating items in a Haskell Map, how?

I am new to Haskell and am trying to figure out a sensible way to write to Maps (in preparation for solving a particular Euler project problem)

I hope to write a function that would populate a Map with a record. But I can't get it to work.

let seems to create local variables instead of
treating smap as a global.

There must be a some way to do this.

My code:

import Data.Map (Map)
import qualified Data.Map as Map 

smap = Map.fromList [("cocoa",23)]


newdata str n = do  
   let cpy  = Map.insert str n  smap
   cpy 

main = do
     let smap = newdata "pennywise" 16  
     let smap = newdata "krusty" 18  

update from comments: Later on I want to count how many ways a right angle triangle is equal to a perimeter. So I thought a Map would be a good way to store the distribution counts e.g. p10 -> 5 ways, p15 -> 6 ways etc. So as the program runs, it would increment already discovered perimeters values.

like image 288
Kevin Avatar asked Jul 04 '18 19:07

Kevin


2 Answers

You can not modify a Map in place (since Haskell is a purely functional language) but you can create a new one which is almost equal to the old map, except for a few entries that have been modified.

(Don't worry too much about efficiency: counter-intuitively, the new Map does not require a full copy of the old one.)

For instance, suppose we want to count the frequencies of each character in a string. Let's write a function which, given a char c, increments its count stored inside the Map

import qualified Data.Map.Strict as M

countChar :: Char -> M.Map Char Int -> M.Map Char Int
countChar c oldMap = newMap
   where
   newMap = M.insertWith (+) c 1 oldMap

The newMap variable is not needed, it is shown above for clarity.

The function insertWith makes the new map so that at index c it stores 1, if there is no value in the old map, or 1 + x if there is a previous value x in the old map.

To handle a full string, we use recursion:

countString :: String -> M.Map Char Int
countString ""     = M.empty
countString (c:cs) = countChar c (countString cs)

Small test in GHCi:

> countString "here's an example"
fromList [(' ',2),('\'',1),('a',2),('e',4),('h',1),('l',1),('m',1)
         ,('n',1),('p',1),('r',1),('s',1),('x',1)]

For a more advanced solution, countString could also be rewritten as a fold, if wanted. Using a left strict fold would also improve efficiency.

countString = foldl' (flip countChar) M.empty

One could even use a state monad to avoid passing around the Map. If you are learning Haskell, don't worry about this, and start by learning how to solve these kinds of tasks using recursion, pattern matching, and a few library functions for Maps.

like image 171
chi Avatar answered Sep 29 '22 06:09

chi


Haskell is a pure functional language. You can not modify a variable in place. But with state monad and lens, Haskell can be the best imperative language. Here is an example.

import Control.Lens
import Data.Map
import Control.Monad.State

example :: State (Map String Int) Int
example = do
    -- set value
    at "pennywise" ?= 16
    at "krusty" ?= 18
    -- get value
    Just krusty <- use $ at "krusty"
    pure krusty

main :: IO ()
main = do
    let r = evalState example empty
    print r

Lens provides a common interface to operate with data structures like Map, that's why I love it.

Another example:

countString :: String -> Map Char Int
countString str = flip execState empty $
    forM_ str $ \c -> 
        at c %= Just . maybe 1 (+1)

-- countString "asasdsas"
-- fromList [('a',3),('d',1),('s',4)]
like image 29
wul Avatar answered Sep 29 '22 08:09

wul