我正在Haskell中编写一个代码,其中包含0和1的列表,如[1,1,0,0,1,0,1]返回一对(元组)在(3,4)列表中出现0和1的数字。
这是我的代码:
inc :: Int -> Int
inc x = (\x -> x + 1) x
count :: [Int] -> (Int,Int)
c = (0,0)
count x =
if null x
then c
else if head x == 0
then do
inc (fst c)
count (tail x)
else if head x == 1
then do
inc (snd c)
count (tail x)
我也尝试过这种保护形式:
count :: [Int] -> (Int,Int)
c = (0,0)
count x
| null x = c
| head x == 0 = inc (fst c) >> count (tail x)
| head x == 1 = inc (snd c) >> count (tail x)
主要问题是我不确定如何在一个声明中实现两个函数。
答案 0 :(得分:9)
你正在考虑所有人。像do { inc (fst c); count (tail x) }
这样的东西只有在c
是某种可变状态变量时才有意义。 Haskell变量不可变,因此inc
无法修改 fst
c
,它只能为您提供修改后的副本。如果将inc
重写为完全等效的简单形式,则可能会更清楚:
inc x = x + 1
(事实上,inc = (+1)
也会这样做。)
现在,在count
中,您试图通过递归循环继续并递增单个累加器变量。您可以这样做,但您需要明确将已修改的版本传递给递归调用:
count = go (0,0)
where go :: (Int,Int) -> [Int] -> (Int,Int)
go c x
| null x = c
| head x == 0 = go (first inc c) (tail x)
| head x == 1 = go (second inc c) (tail x)
这种定义小型本地帮助函数的模式(go
只是一个任意名称,我也可以称之为getTheCountingDone
)并将其用作“loop body” of the recursion是很常见的哈斯克尔。基本上go (0,0)
“将”c
初始化为值(0,0)
,然后开始第一次循环迭代。对于第二次迭代,您可以递归到例如go (first inc c)
,即您使用更新的c
变量重新开始循环。
我使用first
and second
来增加相应的元组字段。 fst
只有读取第一个字段,即为其提供其值,而first
从元素更新函数创建元组更新函数。您可以自己定义,而不是import Control.Arrow
:
first :: (a->b) -> (a,y) -> (b,y)
first f (a, y) = (f a, y)
second :: (a->b) -> (x,a) -> (x,b)
second f (x, a) = (x, f a)
(Control.Arrow
版本实际上更为通用,但您无需担心 - 您可以以同样的方式使用它。)
请注意,在Haskell中很难避免使用head
和tail
解构列表:很容易出错 - 在访问元素之前,您可能忘记检查列表是否为空,这会引起讨厌运行时错误。更好地使用模式匹配:
count = go (0,0)
where go c [] = c
go c (0:xs) = go (first inc c) xs
go c (1:xs) = go (second inc c) xs
实际上这仍然不安全:你没有详尽的案件;如果列表包含除零或1之外的任何内容,则函数将失败。也许您想要计算所有零和非零元素?
count = go (0,0)
where go c [] = c
go c (0:xs) = go (first inc c) xs
go c (_:xs) = go (second inc c) xs
答案 1 :(得分:1)
另一种选择
> import Data.List(group,sort)
> count = tuplify . map length . group . sort
where tuplify [x,y] = (x,y)
答案 2 :(得分:1)
一种解决方案是过滤列表两次,一次保留零,一次保留:
count :: [Int] -> (Int, Int)
count nums = (length (filter (0 ==) nums), length (filter (1 ==) nums))
答案 3 :(得分:0)
一个选项是为您的count
函数设置第二个参数,以跟踪您已计算的内容:
count :: [Int] -> (Int, Int) -> (Int, Int)
-- if the list is empty, return the ones and zeroes already counted
count [] (zeroes, ones) = (zeroes, ones)
-- if first element is a 0, increment the existing count for zeroes
-- and count the rest
count (0:more) (zeroes, ones) = count more (zeroes + 1, ones)
-- as before, but the first element is a 1
count (1:more) (zeroes, ones) = count more (zeroes, ones + 1)
当我们拨打电话时,我们必须给它一个“开始计数”' (0,0)
:
count [1,0,1,1,1,0,0,1] (0,0)
,返回(3,5)
,因为初始对中的第一个0被列表中的零增加3倍,而初始对中的第二个0增加了列表中的0。
此解决方案是一种常见的函数式编程风格,称为“累积参数”。