Haskell如何对列表中的字符串进行计数和分组

时间:2015-02-21 16:20:04

标签: haskell

我正在学习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。我无法找到一种递归方法来修复它。

5 个答案:

答案 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

当你遍历列表进行计数时,将xresult传递给checkCount函数,在这种情况下,result函数将遍历x。如果找到x的匹配项,则会增加resultx的计数。如果找不到(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.MapData.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)]