我想从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
的内容根本不会改变。
答案 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