我正在学习haskell并需要一些帮助来确定这个函数的逻辑。我只想在可能的情况下使用标准前奏和递归中的函数来解决问题。
所以我有一系列的事情,例如:
["Abhi", "Stack","how", "Abhi"]
目标是将其转换为带有计数和列表项的对列表
输出:[("Abhi", 2), ("Stack",1)..]
我提出了以下功能
countList :: [String] -> [(Int,String)]
countList [] = []
countList xss@(x:xs) = (x,checkCount x xss) : countList xs
checkCount :: String -> [String] -> Int
checkCount _ [] = 0
checkCount str (x:xs) | str == x = 1 + checkCount str xs
| otherwise = 0 + checkCount str xs
我得到的输出是:
[("Abhi",2), ("Stack",1), ("how",1),("Abhi",1)]
注意最后一项是Abhi,1。我无法找到一种递归方法来修复它。
答案 0 :(得分:1)
要使用Prelude
函数和递归执行此操作,您可以使用元组列表模拟Map
结构。这样效率会降低,但它避免了必须使用其他模块。该算法的核心只是编写一个更新Map
:
type Map k v = [(k, v)] -- Simulate Data.Map.Map
update :: Eq k => k -> (Maybe v -> v) -> Map k v -> Map k v
update k fv [] = [(k, fv Nothing)]
update k fv ((k', v'):rest)
= if k == k'
then (k', fv $ Just v') : rest
else (k', v') : update k fv rest
这个函数只是说走向Map
,如果找到元素然后更新它并停止,否则继续尝试。如果它到达Map
的末尾,则使用Nothing
调用更新函数以生成默认值。
接下来,我们可以使用此功能为此特定情况构建Map k Int
:
countOccurs :: Eq k => [k] -> Map k Int
countOccurs = foldr go []
where
go k = update k updtr
updtr Nothing = 1
updtr (Just i) = i + 1
但我们可以将updtr
内联为maybe 1 (+1)
:
countOccurs = foldr go []
where
go k = update k (maybe 1 (+1))
由于go
非常简单,我们只需使用flip
内联它,或等效使用反引号来制作update
中缀:
countOccurs = foldr (`update` maybe 1 (+1)) []
我们甚至不需要在maybe 1 (+1)
附近放置parens,因为它现在被视为操作员的参数。
作为演示:
> countOccurs $ words "Abhi Stack how Abhi"
[("Abhi", 2), ("how", 1), ("Stack", 1)]
> countOccurs [1, 1, 2, 3, 2, 2, 1, 1]
[(1, 4), (2, 3), (3, 1)]
答案 1 :(得分:0)
您的代码中的一个问题是代码段countList xs
- 这意味着您不计算完整列表中每个x
的出现次数,而是每次调用countList
时减少的次数。
为什么不在表格countList
的原始[(Int,String)]
中添加参数 - 让我们称之为result
。
当你遍历列表进行计数时,将x
和result
传递给checkCount
函数,在这种情况下,result
函数将遍历x
。如果找到x
的匹配项,则会增加result
中x
的计数。如果找不到(1,x)
,则会在result
中插入{{1}}。
答案 2 :(得分:0)
bheklilr - 感谢您的精彩回答和解释。由于我是Haskell的新手,所以在开始时我的头脑很少。
无论如何,我找到了另一种方法。我改变了使用索引列出递归的方法。它可能不是最佳的,但至少可行。在这里
最初我使用列表值进行迭代,因此,我无法解释已删除的值。我决定按原样保留列表并使用索引进行迭代。
countList'是一个包装器,因此,我不必通过列表的长度。
countList' :: [String] -> [(Int,String)]
countList' [] = []
countList' lst = countListWithIndex (length' lst) lst
CheckCount功能未改变
checkCount :: String -> [String] -> Int
checkCount _ [] = 0
checkCount str (x:xs) | str == x = 1 + checkCount str xs
| otherwise = 0 + checkCount str xs
此函数根据索引获取列表中的重复次数。
countListWithIndex :: Int -> [String] -> [(Int, String)]
countListWithIndex 0 _ = []
countListWithIndex n str = (checkCount string str, string) : countListWithIndex (n-1) str
where string = dataAt (n-1) str
使用索引
在列表中查找数据的功能dataAt :: Int -> [a] -> a
dataAt _ [] = error "Empty List!"
dataAt y (x:xs) | y <= 0 = x
| otherwise = dataAt (y-1) xs
计算列表长度的简单函数
length' :: [a] -> Int
length' [] = 0
length' (_:xs) = 1 + length' xs
另外,我知道列表仍然有重复的值。我可能要编写另一个函数来删除它们。宝贝步骤:)
无论如何都很想知道反馈。
答案 3 :(得分:0)
您的函数countList
(checkCount没问题)发生在此行countList xss@(x:xs) = (x,checkCount x xss) : countList xs
上。当你再次致电countList
时,你失去了头x
。所以,第一个递归调用将是:
("Abni",2) : countList ["Stack","how", "Abhi"]
当它到达下一个"Abni"
时,只会有一个。
您需要保留原始列表。试试这个:
countList' :: [String] -> [(String, Int)]
countList' [] = []
countList' (s:ss) = fun s ss (s:ss) []
where fun s ss sss acc
| null ss = (s, checkCount' s sss):acc
| otherwise = fun (head ss) (tail ss) sss $ (s, checkCount' s sss):acc
以上是尾递归。更简单:
countList :: [String] -> [(String, Int)]
countList [] = []
countList ss = hack ss ss
where hack (x:xs) sss
| null xs = [(x, checkCount x sss)]
| otherwise = (x, checkCount x sss) : (hack xs sss)
答案 4 :(得分:0)
以下是使用一系列辅助函数执行此操作的一种方法。我认为额外的详细程度是可以的,因为解决方案只需要使用Prelude ...一个简洁的解决方案是直接应用已经存在于Data.Map
或Data.List
中的辅助函数。可能使用fold
等等我可以缩短我的一些内容......
countByGroup :: (Eq a) => [a] -> [(a, Int)]
countByGroup [] = []
countByGroup xs = let (c:cs) = toCounts xs
in mergeCounts [c] cs
toCounts :: [a] -> [(a, Int)]
toCounts xs = [(x, 1) | x <- xs]
mergeCounts :: (Eq a) => [(a, Int)] -> [(a, Int)] -> [(a, Int)]
mergeCounts = mergeCounts' []
mergeCounts' :: (Eq a) => [(a, Int)] -> [(a, Int)] -> [(a, Int)] -> [(a, Int)]
mergeCounts' acc [] [] = acc
mergeCounts' acc (l:ls) [] = case ls of
[] -> acc ++ [l]
otherwise -> acc ++ mergeCounts [l] ls
mergeCounts' acc [] (r:rs) = case rs of
[] -> acc ++ [r]
otherwise -> acc ++ mergeCounts [r] rs
mergeCounts' acc (l:ls) rs =
let rSum = sum [snd r | r <- rs, fst r == fst l]
lSum = sum [snd x | x <- ls, fst x == fst l]
newAcc = acc ++ [(fst l, snd l + rSum + lSum)]
newRs = [r | r <- rs, fst r /= fst l]
newLs = [x | x <- ls, fst x /= fst l]
in mergeCounts' newAcc newLs newRs
例如:
*Main> countByGroup ["a", "b", "a", "b"]
[("a",2),("b",2)]
*Main> countByGroup ["Abhi", "Stack","how", "Abhi"]
[("Abhi",2),("Stack",1),("how",1)]
*Main> countByGroup ["Abhi", "Stack","how", "Abhi", "how"]
[("Abhi",2),("Stack",1),("how",2)]
*Main> countByGroup ["Abhi", "Abhi"]
[("Abhi",2)]
*Main> countByGroup []
[]
*Main> countByGroup ["how", "Abhi", "Abhi", "how", "how"]
[("how",3),("Abhi",2)]