Haskell:如何逐行读取stdin中的值并将它们添加到地图中?

Ash*_* H. 4 stdin haskell map

我想从stdin读取字符串并将它们存储到地图中,其中key是输入字符串,并且value是此字符串之前出现的次数.在Java中我会做这样的事情:

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));
    }
}
Run Code Online (Sandbox Code Playgroud)

我已经尝试过在Haskell中使用forM_但是没有运气.

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)
Run Code Online (Sandbox Code Playgroud)

dataset上面代码中的内容根本没有变化.

asm*_*asm 5

你的问题是Map.insert不能map.remove用C++ 做什么. Map.insert返回一个新的Map,其中包含元素,但你只是抛弃了这个新的Map.这就是几乎所有Haskell数据结构的工作方式,例如代码:

main = do
  let x = []
      y = 5 : x
  print x
Run Code Online (Sandbox Code Playgroud)

打印空列表[].cons :运算符不会破坏性地修改空列表,但返回包含的新列表5. Map.insert使用地图而不是列表执行相同操作.


bhe*_*ilr 5

Map定义中Data.Map是不可变的数据类型.调用Map.insert返回修改后的内容Map,它不会更改您已有的内容.您想要做的是在循环中迭代地应用更新.更喜欢的东西

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
Run Code Online (Sandbox Code Playgroud)

注意这实际上是如何减少代码(如果你只计算出现次数,它会更短updateMap dataset str = M.insertWith (+) str 1 dataset),它将纯代码与不纯的代码分开.

在这种情况下,您实际上并不想使用forM_,因为计算的每个步骤都取决于之前的步骤.编写一个在某种条件下退出的递归函数是首选.如果你需要的话,你也可以写loop

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
Run Code Online (Sandbox Code Playgroud)

在这里,我将旧的主体压缩loop成一行,然后将其放入内部go,这允许您将其称为

main :: IO ()
main = do
    n <- fmap read getLine :: IO Int
    dataset <- loop n
    print dataset
Run Code Online (Sandbox Code Playgroud)

这消除了需要知道,你必须传递M.emptyloop了第一个电话,除非你有一个用例调用loop同一个地图上多次.