如何测试haskell中的count_if函数

时间:2012-12-15 18:55:12

标签: testing haskell

我正在尝试编写一个接受谓词和列表的函数,并返回一个满足谓词的列表。

所以,例如,我想要这样的东西:

haskell> count_if (x > 3) [2,3,4,5,6] 
[4,5,6]

这是我到目前为止所拥有的:

count_if f [] = 0 
count_if f (x:xs) 
  | f x = x : count_if f xs
  | otherwise = count_if f xs 

我的问题是,如何使用谓词测试此函数?

4 个答案:

答案 0 :(得分:4)

为此目的有一个函数filter。无论如何,要测试count_if或filter,你可以做类似

的事情
filter (>3) [2,3,4,5,6]

答案 1 :(得分:3)

你的countif函数正在努力计算任何东西,因为你告诉它列出一个列表:

count_if f [] = 0  -- fine, makes sense
count_if f (x:xs) 
  | f x = x : count_if f xs  -- Oops, no!
  | otherwise = count_if f xs  -- yup

请注意1:[2,3] = [1,2,3],因此:用于在列表的前面添加额外的元素。如果你想计算,你想要一个数字,而不是一个列表。 (将x放在前面听起来很像filter,它会为你提供谓词为真的所有元素,但你想要计算,这是不同的。)

如果通过提供类似count_if :: (a -> Bool) -> [a] -> Int的显式类型签名告诉编译器您期望的内容,您将更容易发现此类错误。不要将x放在x:的前面,让我们添加一个1+,给予

count_if :: (a -> Bool) -> [a] -> Int
count_if f [] = 0 
count_if f (x:xs) 
  | f x = 1 + count_if f xs  -- adds one to the total from the rest
  | otherwise = count_if f xs  

现在可以这样测试:

> count_if (>5) [1..10]
5
> count_if (=='e') "Time is of the essence"
5
> count_if even [1..100]
50

现在您可以使用count_if制作filter。过滤器的类型为filter :: (a -> Bool) -> [a] -> [a],它只提供您需要的元素:

> filter (>5) [1..10]
[6,7,8,9,10]
> filter (=='e') "Time is of the essence"
"eeeee"

然后在结果上做长度:

countif' :: (a -> Bool) -> [a] -> Int
countif' f xs = length (filter f xs)

但是,这可以写得更整洁了

countif :: (a -> Bool) -> [a] -> Int
countif f = length . filter f 

因为.是函数组合 - 这表示使用f过滤,然后取长度。

(Pointfree极客宁愿把它写成countif = (length.).filter,但那是另一天的教训!)

使用filterlength等标准功能可能会导致您自己无法发现的性能增强。如果您针对countif (>0) [1..1000000]count_if (>0) [1..1000000]进行测试,您会发现它的运行速度明显加快。因此,从前奏中了解诸如filterfoldrscanr之类的前奏函数是一个好主意。

答案 2 :(得分:2)

有几种方法可以编写谓词。有一些内置函数是谓词,例如:

even :: Integer -> Bool
odd  :: Integer -> Bool
null :: [a] -> Bool

您可以将运算符部分与比较运算符一起使用:

(== 0) :: Integer -> Bool
(3 >)  :: Integer -> Bool

或者您可以使用lambda表达式编写更复杂的谓词:

(\x -> 1 < x && x < 5) :: Integer -> Bool

例如,您应该:

count_if even [1,2,3,4,5,6]                   -->  [2,4,6]
count_if (3 >) [1,2,3,4,5,6]                  -->  [1,2]
count_if (\x -> 1 < x && x < 5) [1,2,3,4,5,6] -->  [2,3,4]

答案 3 :(得分:1)

该过滤器的现有功能

请参阅:http://hackage.haskell.org/packages/archive/base/latest/doc/html/Prelude.html#v:filter

编辑:

所以问题是你的实现几乎是正确的:

count_if f [] = 0 

需要

count_if :: (a -> Bool) -> [a] -> [a]
count_if f [] = []
count_if f (x:xs) 
| f x = x:count_if f xs
| otherwise = count_if f xs 

如果你指定函数的类型会有帮助,那么编译器会帮助你