在Haskell中添加两个存储为0和1的列表的二进制数,而不使用反向

时间:2016-12-24 22:49:12

标签: list haskell recursion binary

我试图以优雅和富有表现力的方式解决这个简单的问题。通常,我会从两个列表的末尾开始,添加相应的元素并存储进位以计算下一个数字。但是,我正在努力通过递归和不使用reverse函数来解决这个问题。

这是我的第一次尝试:

binarySum :: [Int] -> [Int] -> [Int]
binarySum ls ls'
  = let (res, c) = binarySum' ls ls' in c : res
  where
binarySum' [x] [y]
  = let (s, c) = add x y in ([s], c)
binarySum' (x : xs) (y : ys)
  = (s : res, c')
  where
    (res, c) = binarySum' xs ys
    (s, c')  = add' x y c

(添加和添加功能执行所需操作的地方)

结果列表似乎是正确的,但顺序相反。我不知道如何继续,因为我选择在辅助函数中随身携带返回的对中构建结果(通常我会做s : binarySum'...之类的事情。)

此外,我觉得代码太杂乱了,不像应该的那样优雅。

非常感谢任何帮助!

2 个答案:

答案 0 :(得分:2)

你几乎就在那里(至少你的解释似乎表明 - 你的代码依赖于你还没有包含的add函数)。诀窍确实是将进位作为一个单独的数字保存在辅助函数的元组中(我将其命名为binarySum')。您正在使用的不变量是返回的列表与提供的两个列表中较大的列表具有相同的长度(并且是它们总和的第一个数字) - 如果有任何值,则单独保存。

binarySum :: [Int] -> [Int] -> [Int]
binarySum xs ys
  | length xs < length ys = binarySum (replicate (length ys - length xs) 0 ++ xs) ys
  | length xs > length ys = binarySum xs (replicate (length xs - length ys) 0 ++ ys)
  | otherwise = case binarySum' xs ys of
                    (0, zs) -> zs
                    (1, zs) -> 1:zs
  where
    binarySum' :: [Int] -> [Int] -> (Int, [Int])
    binarySum' [] ys = (0, ys)
    binarySum' xs [] = (0, xs)
    binarySum' (x:xs) (y:ys) = let (c, zs) = binarySum' xs ys
                                   (c', z) = (x + y + c) `divMod` 2
                               in (c', z:zs)

答案 1 :(得分:0)

晚了四年。不过我不在乎:

(+++) :: [Int] -> [Int] -> [Int]
(+++) n1 n2 = f2 $ foldr f1 ([],0) (copulate n1 n2)
  where
    -- immitates `zip`
    -- but it will assures that both lists are of both length
    copulate :: [Int] -> [Int] -> [(Int,Int)]
    copulate n1 n2 | length n1 == length n2 = zip n1 n2
    copulate n1 n2 | length n1 > length n2  = zip n1 (replicate (length n1 - length n2) 0 ++ n2)
    copulate n1 n2 | length n1 < length n2  = zip (replicate (length n2 - length n1) 0 ++ n1) n2
    
    f1 :: (Int,Int) -> ([Int],Int) -> ([Int],Int)    
    f1 (0,0) (res,0) = ([0]++res,0)
    f1 (0,0) (res,1) = ([1]++res,0)

    f1 (1,0) (res,0) = ([1]++res,0)
    f1 (1,0) (res,1) = ([0]++res,1)

    f1 (0,1) (res,0) = ([1]++res,0)
    f1 (0,1) (res,1) = ([0]++res,1)

    f1 (1,1) (res,0) = ([0]++res,1)
    f1 (1,1) (res,1) = ([1]++res,1)
    f1 _     _       = error "can only process binary bits"  


    f2 :: ([Int],Int) -> [Int]
    f2 (list,0) = list
    f2 (list,1) = [1] ++ list

foldr 非常适合从右侧遍历而无需反转。此外,整个列表都在处理中,函数在f1中严格严格,这使得foldr非常适合这个操作。

看看 foldr 的累加器。它在开头是一个元组:([],0).

  • 元组中的第一个元素将存储结果。
  • 元组中的第二个元素将存储添加每个位的“状态”。如果其值为 1,此“状态”将影响后续元组对。

现在你可以测试了:[1,0,1] +++ [1,0,0] 将给出 1001 感谢中缀运算符,如果您愿意,您可以链接更多的加法,例如:[1,0,1] +++ [1,0,0] +++ [1,0,0, 0,0,0,0]

尽管答案看起来很长,但它很容易合理化和编写,这使得它在逻辑上不冗长,而且我发现这个解决方案比递归更容易。