如何在声明中写两个声明?

时间:2016-11-22 01:33:43

标签: if-statement haskell tuples

我正在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)

主要问题是我不确定如何在一个声明中实现两个函数。

4 个答案:

答案 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中很难避免使用headtail解构列表:很容易出错 - 在访问元素之前,您可能忘记检查列表是否为空,这会引起讨厌运行时错误。更好地使用模式匹配

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。

此解决方案是一种常见的函数式编程风格,称为“累积参数”。