在Haskell中不使用+运算符将两个数字相加

时间:2018-11-02 10:03:15

标签: haskell addition

我想将两个正数加在一起,而不使用+之类的任何基本运算符。我已经解决了这个问题(在add'''函数中)(我认为)可能没有效率,但是那不是现在的重点。我遇到了很多类型错误,但是我不知道如何处理,并且对我来说非常困惑,因为它可以在纸上工作并且我来自python。

添加1245 7489

--add :: Int -> Int -> Int
add x y = add'' (zip (add' x) (add' y))
 where
 add' :: Int -> [Int]
 add' 0 = []
 add' x = add' (x `div` 10) ++ [x `mod` 10]

转换[1,2,4,5] [7,4,8,9],然后将它们压缩在一起[[1,7),(2,4)....]

 add'' :: [(Int,Int)] -> [Int]
 add'' (x:xs) = [(add''' (head x) (last x))] ++ add'' xs 

总结[8,6,...]当总和达到10时会发生什么。

  where
  --add''' :: (Int,Int) -> Int
  add''' x y = last (take (succ y) $ iterate succ x)

将两个数字加在一起

4 个答案:

答案 0 :(得分:3)

  1. 您不能在元组上使用headlast。 ...坦率地说,您不应该完全使用这些功能,因为它们不安全(部分),但是它们可以在列表中使用。在Haskell中,列表与元组完全不同。
    要使用元组的元素,请使用模式匹配

    add'' ((x,y):xs) = [add''' x y] ++ add'' xs 
    

    (要获得列表中的元素,模式匹配通常也是最好的选择。)或者,您可以使用fstsnd,它们在2元组上执行您显然认为的操作headlast

  2. 要明确哪些函数是咖喱的,哪些不是。编写add'''的方式,其类型签名实际上是Int -> Int -> Int。与(Int, Int) -> Int等价(em),但与类型检查器仍不相同。

  3. add''的结果为[Int],但是您正在尝试将Int的结果用作add。那行不通,您需要再次将数字转换为数字。

  4. add''不处理空情况。这很容易解决,但比起使用标准组合器进行所有递归要好。就您而言,这仅应以元素方式工作,因此您可以简单地使用map –或直接在zipWith的压缩中进行操作。然后,您也根本不需要解开任何元组,因为它可以使用curried函数。


您尝试的干净版本:

add :: Int -> Int -> Int
add x y = fromDigits 0 $ zipWith addDigits (toDigits x []) (toDigits y [])
 where
       fromDigits :: Int -> [Int] -> Int
       fromDigits acc [] = acc
       fromDigits acc (d:ds)
            = acc `seq`  -- strict accumulator, to avoid thunking.
                fromDigits (acc*10 + d) ds

       toDigits :: Int -> [Int] -> [Int]                -- yield difference-list,
       toDigits 0 = id                                  -- because we're consing
       toDigits x = toDigits (x`div`10) . ((x`mod`10):) -- left-associatively.

       addDigits :: Int -> Int -> Int
       addDigits x y = last $ take (succ x) $ iterate succ y

请注意,zipWith要求两个数字都具有相同的数字位数(与zip一样)。

此外,是的,我在+中使用fromDigits,这使整个事情变得毫无用处。实际上,您当然会使用二进制,那么它只是按位或,而乘法是左移。您实际上不需要要做的是特别小心10溢出,但这只是因为在+中使用fromDigits的作弊。

答案 1 :(得分:1)

headlast的意思是fstsnd,但您根本不需要它们,组件就在那里:

add'' :: [(Int, Int)] -> [Int]
add'' (pair : pairs) = [(add''' pair)] ++ add'' pairs 
  where
  add''' :: (Int, Int) -> Int
  add''' (x, y) = last (take (succ y) $ iterate succ x)
                = iterate succ x !! y
                = [x ..] !! y          -- nice idea for an exercise!

现在剩下的主要问题是如何处理那些 big 个可怕的10位数以上的数字。这是一个想法:用-产生一个数字和一个进位

   = ([(d, 0) | d <- [x .. 9]] ++ [(d, 1) | d <- [0 ..]]) !! y

可以从这里拿走吗?提示:数字的倒序是你的朋友!

答案 2 :(得分:1)

我的教授给出的正式答案

也可以处理正数和负数,但仍要求两个数字的长度相同

add 0 y = y
add x y
 | x>0 = add (pred x) (succ y)
 | otherwise = add (succ x) (pred y)

答案 3 :(得分:0)

其他答案涵盖您的方法中出了什么问题。但是,从理论上讲,它们各自都有一些缺点:它们要么使您进入[Int]而不是Int,要么在从(+)转换为[Int]的过程中使用Int mod。而且,他们在定义加法时将divmod用作子例程-可以,但是从理论上讲,您需要确保可以定义div+1本身,而不使用加法作为子例程!

由于您说效率无关紧要,所以我建议使用数学家给出的通常加法定义,即:0 + y = y,并且(x + 1)+ y =(x + y)+1。在这里,您应该将succ读作一个单独的操作,而不是加法,这是一个更原始的操作:仅增加一个数字的操作。我们在Haskell中将其拼写为pred(其“倒数”为add :: Int -> Int -> Int add 0 y = y add x y = succ (add (pred x) y) )。牢记这一理论定义,Haskell几乎可以自己撰写:

Int

因此:与其他答案相比,我们可以使用Int并返回succ,并且我们使用的唯一子例程是“感觉”更原始的子例程:pred,{ {1}},并检查数字是零还是非零。 (而且我们仅使用三行短代码……大约是建议的最短替代代码的三分之一。)当然,我们付出的代价是非常糟糕的性能……尝试add (2^32) 0

与其他答案一样,这仅适用于正数。当您准备好处理负数时,我们应该再次聊天-有fascinating mathematical tricks可以拉。