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 Map
s.
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