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

时间:2013-12-02 20:53:52

标签: haskell map stdin

我想从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));
    }
}

我尝试使用forM_在Haskell中做同样的事情,但没有运气。

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)

上述代码中dataset的内容根本不会改变。

3 个答案:

答案 0 :(得分:5)

您的问题是Map.insert没有执行map.remove在C ++中所做的事情。 Map.insert返回一个新的Map,其中包含元素,但您只是抛弃了这个新的Map。这就是几乎所有Haskell数据结构的工作方式,例如代码:

main = do
  let x = []
      y = 5 : x
  print x

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

答案 1 :(得分: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 。您想要做的是在循环中迭代地应用更新。更像是

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 写为

loop

在这里,我将旧go的正文压缩成一行,然后将其放在main :: IO () main = do n <- fmap read getLine :: IO Int dataset <- loop n print dataset 内,这样就可以将其称为

M.empty

除非您有一个用例在同一张地图上多次调用loop,否则无需知道您必须将loop传入{{1}}进行第一次调用。

答案 2 :(得分:0)

首先关于你的java代码,你不需要在插入新值之前从地图中删除。

关于haskell,语言不像你想象的那样工作:你的let技巧不是更新值,所有内容在haskell中基本上都是不可变的。

仅使用基本的getLine,一种方法是使用递归:

import qualified Data.Map as Map


type Dict = Map.Map String Int

makeDict ::Dict -> Int -> IO Dict
makeDict d remain = if remain == 0 then return d else do
  l <- getLine
  let newd = Map.insertWith (+) l 1 d
  makeDict newd (remain - 1)

newDict count = makeDict Map.empty count