I want to read strings from stdin and store them into a map, where key
is the input string and value
is the number of previous occurrences of this string. In Java I would have done something like this:
for (int i = 0; i < numberOfLines; i++) {
input = scanner.nextLine();
if (!map.containsKey(input)) {
map.put(input, 0);
System.out.println(input);
} else {
int num = map.get(input) + 1;
map.remove(input);
map.put(input, num);
System.out.println(input.concat(String.valueOf(num));
}
}
I've tried doing the same in Haskell by using forM_
but had no luck.
import Control.Monad
import qualified Data.Map as Map
import Data.Maybe
main = do
input <- getLine
let n = read input :: Int
let dataset = Map.empty
forM_ [1..n] (\i -> do
input <- getLine
let a = Map.lookup input dataset
let dataset' =
if isNothing a then
Map.insert input 0 dataset
else
Map.insert input num (Map.delete input dataset)
where num = ((read (fromJust a) :: Int) + 1)
let dataset = dataset'
let output = if isNothing a then
input
else
input ++ fromJust a
putStrLn output)
The contents of dataset
in the above code does not change at all.
The Map
defined in Data.Map
is an immutable data type. Calling Map.insert
returns a modified Map
, it does not change the one you already have. What you want to do is iteratively apply updates in a loop. Something more like
import qualified Data.Map as M
import Data.Map (Map)
-- Adds one to an existing value, or sets it to 0 if it isn't present
updateMap :: Map String Int -> String -> Map String Int
updateMap dataset str = M.insertWith updater str 0 dataset
where
updater _ 0 = 1
updater _ old = old + 1
-- Loops n times, returning the final data set when n == 0
loop :: Int -> Map String Int -> IO (Map String Int)
loop 0 dataset = return dataset
loop n dataset = do
str <- getLine
let newSet = updateMap dataset str
loop (n - 1) newSet -- recursively pass in the new map
main :: IO ()
main = do
n <- fmap read getLine :: IO Int -- Combine operations into one
dataset <- loop n M.empty -- Start with an empty map
print dataset
Notice how this is actually less code (it's be even shorter if you just counted the number of occurrences, then updateMap dataset str = M.insertWith (+) str 1 dataset
), and it separates the pure code from the impure.
In this case, you don't actually want to use forM_
, because each step of the computation depends on the previous. It's preferred to write a recursive function that exits at a condition. If you so desired, you could also write loop
as
loop :: Int -> IO (Map String Int)
loop n = go n M.empty
where
go 0 dataset = return dataset
go n dataset = getLine >>= go (n - 1) . updateMap dataset
Here I've compressed the body of the old loop
into a single line and then put it inside go
, this allows you to call it as
main :: IO ()
main = do
n <- fmap read getLine :: IO Int
dataset <- loop n
print dataset
This removes the need to know that you must pass in M.empty
into loop
for the first call, unless you have a use case to call loop
multiple times on the same map.
Your problem is that Map.insert
does not do what map.remove
does in C++. Map.insert
returns a new Map which has the element in it but you are simply throwing this new Map away. This is how nearly all Haskell data structures work, for instance the code:
main = do
let x = []
y = 5 : x
print x
prints the empty list []
. The cons :
operator does not destructively modify the empty list but returns a new list containing 5
. Map.insert
does the same but with Maps instead of lists.
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