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.
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.
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)]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With