我想做一个Haskell函数,其中输入(一个字符串列表)是有序的(总是。输入只有在有序的情况下才有效),并且我想获取每个不同字符串的出现次数。
示例:
ContaOcs["a", "a", "b", "c", "c", "c", "d"]
应返回:
[(2,"a"), (1,"b"), (3,"c"), (1,"d")]
这是我想要做的事情:
module Main where
contaOcs :: [String] -> [(Int, String)]
contaOcs [] = [_,_]
contaOcs [x] = [1,x]
contaOcs (i, x1:x2:xs)
| x1 == x2 = (i+1,(x2:xs))
| otherwise = (0, (x2:xs))
但是此代码有一些错误,我不确定如何完成此操作 我是函数式编程和Haskell的新手。有人可以帮我一些信息吗?
感谢您的帮助。
答案 0 :(得分:8)
存在一些语法问题以及类型问题。第一行如下:
contaOcs [] = [_,_]
但是结果中的下划线(_
)没有任何意义,您只能构造包含值的列表。当我们计算一个空列表的出现次数时,结果将是一个空列表,因此contaOcs [] = []
。
第二个:
contaOcs [x] = [1,x]
此处,您的目标是返回包含两个元素的列表:1
和x
(即String
)。在Haskell中,列表的元素都具有 same 类型。您可以做的是返回一个包含两个元组的列表,其中第一个为Int
,第二个为String
,如签名所示,但是随后您需要将值包装在2-中元组,例如contaOcs [x] = [(1,x)]
。
在最后一个子句中,您写:
contaOcs (i, x1:x2:xs) = ...
没什么意义:输入类型是一个列表(此处为String
个),而不是带有Int
的2元组和一个字符串列表。
所以输入看起来像:
contaOcs (x1:x2:xs) = ...
输出(如(i+1,(x2:xs))
也不与签名中建议的输出类型“和谐”,它看起来像是带有Int
的2元组和{{1 }},所以String
,而不是(Int, [String])
。
基于以上评论,我们得出了类似的内容:
[(Int, String)]
所以现在有两部分需要填写。如果contaOcs :: [String] -> [(Int, String)]
contaOcs [] = []
contaOcs [x] = [(1,x)]
contaOcs (x1:x2:xs)
| x1 == x2 = -- ...
| otherwise = -- ...
和x1
不相等,则意味着我们可以首先生成一个元组{{1} },然后在列表的其余部分(包括x2
)后面跟随(1, x1)
的结果,因此:
contaOcs
在后一种情况下,这意味着我们首先使用x2
对(1, x1) : contaOcs (x2:xs)
进行递归调用,然后递增该列表第一项的计数器。我们确信该元素存在,因为我们使用包含至少一个个元素的列表进行递归调用,并且通过归纳,这意味着结果也至少包含一个元素,因为基本情况包含一个元素,并且递归案例要么将元素添加到结果中,要么更新这些元素。
因此,我们可以使用模式防护,并操纵结果,例如:
contaOcs
我们还可以使用“ as-pattern ”:我们只需要引用以(x2:xs)
开头的列表尾部,而不是contaOcs :: [String] -> [(Int, String)]
contaOcs [] = []
contaOcs [x] = [(1,x)]
contaOcs (x1:x2:xs)
| x1 == x2, ((yi, yv):ys) <- contaOcs (x2:xs) = (yi+1, yv) : ys
| otherwise = (1, x1) : contaOcs (x2:xs)
:
x2
但是,以上内容并不十分优雅。最好在这里使用 accumulator ,我将其保留为练习。
答案 1 :(得分:4)
让我们看看ghc提到的一些错误。当GHC谈论“预期”和“实际”类型时,请始终格外注意,因为这些消息总是很容易理解。 Expected
表示您应该写什么GHC 。 Actual
表示您写的内容。您要么需要更改编写的内容(阅读:更改您的代码),要么更改GHC认为您应该编写的内容(阅读:更改您的类型注释)。在这种情况下,大多数情况是前者。
hw.hs:2:16: error:
• Found hole: _ :: (Int, String)
• In the expression: _
In the expression: [_, _]
In an equation for ‘contaOcs’: contaOcs [] = [_, _]
• Relevant bindings include
contaOcs :: [String] -> [(Int, String)] (bound at hw.hs:2:1)
|
2 | contaOcs [] = [_,_]
| ^
hw.hs:2:18: error:
• Found hole: _ :: (Int, String)
• In the expression: _
In the expression: [_, _]
In an equation for ‘contaOcs’: contaOcs [] = [_, _]
• Relevant bindings include
contaOcs :: [String] -> [(Int, String)] (bound at hw.hs:2:1)
|
2 | contaOcs [] = [_,_]
| ^
下划线用作占位符(或“空洞”),稍后再填写。 GHC告诉您,您应该找出一些方法可以解决这些问题。
hw.hs:3:19: error:
• Couldn't match type ‘[Char]’ with ‘(Int, String)’
Expected type: (Int, String)
Actual type: String
• In the expression: x
In the expression: [1, x]
In an equation for ‘contaOcs’: contaOcs [x] = [1, x]
|
3 | contaOcs [x] = [1,x]
|
您已声明该函数的返回类型为[(Int, String)]
,换句话说,就是一个List,其中列表的每个元素都是Int
和String
的元组。 / p>
因此,列表中的每个元素应该是一个元组。语法[1,x]
表示具有两个元素的列表:1
和x
。 GHC注意到x
是String
,而不是元组。 (GHC未能注意到1不是元组,原因是……。Haskell中的数字有些奇怪,GHC对此却没有太大帮助。)
也许您打算写(1, x)
,它是1(一个Int
)和x(一个String
)的元组。但是,别忘了也以某种方式将该元组放入列表中,因为您的返回类型是元组的 list 。
hw.hs:4:10: error:
• Couldn't match expected type ‘[String]’
with actual type ‘(Integer, [a0])’
• In the pattern: (i, x1 : x2 : xs)
In an equation for ‘contaOcs’:
contaOcs (i, x1 : x2 : xs)
| x1 == x2 = (i + 1, (x2 : xs))
| otherwise = (0, (x2 : xs))
|
4 | contaOcs (i, x1:x2:xs)
| ^^^^^^^^^^^^^
GHC再次提醒您,它需要一个元组列表,但是在这种情况下,您只给了它一个元组。
错误与此基本相同。
答案 2 :(得分:4)
contaOcs :: [String] -> [(Int, String)]
contaOcs
使用一个字符串列表:xss
,对于xs
中的每个唯一字符串:xss
,我们产生一个对:p
,第一个对元素表示xs
中xss
的出现次数,而p
的第二个元素是xs
本身。
我们知道我们需要按字符串的唯一性对字符串进行分组,并对每个唯一字符串的总出现次数进行计数。您可以遵循这个想法,自己实现其余的想法。 contaOcs
获取一个列表并生成一个新列表,因此列表理解应该可以为您提供所需的内容。您要将一个列表转换为另一个列表,因此fmap可以累加一个函数。您也可以只使用自然递归或累加器。这是写contaOcs
的一种方法:
contaOcs = (return . liftA2 (,) length head =<<) . group
首先写下签名,目的声明,一些示例数据和测试用例,然后找到最适合您需要的解决方案即可。
答案 3 :(得分:2)
单排即可完成
Prelude> import Data.List
Prelude Data.List> ls = ["a", "a", "b", "c", "c", "c", "d"]
Prelude Data.List> [(length x, head x) | x <- group ls]
[(2,"a"),(1,"b"),(3,"c"),(1,"d")]
我将列表理解与group
函数混合使用。您可以自己熟悉的基本概念。
答案 4 :(得分:2)
这是一个很好的例子,说明了协同递归函数很有用。
contaOcs :: [String] -> [(Int, String)]
我们将contaOcs
定义为外部函数,该函数采用字符串列表并返回元组。首先让我们看一下平凡的情况:
contaOcs [] = []
contaOcs [x] = [(1,x)]
传递一个空列表,您应该返回一个空列表。传递单个元素列表,您应该返回一个包含一个元素的列表:(1, x)
。现在我们可以保证其他任何列表的长度都超过2个元素。
contaOcs (x:xs) = go x xs
go
?您可能会问go
是什么?好吧,让我们在where
子句中定义它:
where
go cur xs = let (this, rest) = span (==x) xs
in (succ . length $ this, cur) : contaOcs diff
很多,所以让我们打开包装。 go
是函数助手的惯用语(可以轻松命名为f
或frobnicator
,这无关紧要)。它使用我们要计算的字符(将其与列表的其余部分分开分割),并将其称为x
。它对列表的其余部分运行span (==x)
,将其拆分为元组(longestPrefixThatMatches, rest)
。我们将最长的前缀的长度(加上一个,因为我们已经去除了前面的字符)加上一个元组中的字符本身的长度,然后在递归的情况下进行了验证-将列表的其余部分移回外部函数来处理。
答案 5 :(得分:1)
contaOcs :: [String] -> [(Int, String)]
contaOcs xs = foldr foldContaOcs [] xs
where foldContaOcs s [] = (1, s):[]
foldContaOcs s ((n, ch):xs) = if ch == s then (n + 1, s) : xs
else (1, s): (n, ch): xs