【问题标题】:Adding two numbers together without using the + operator in Haskell在 Haskell 中不使用 + 运算符将两个数字相加
【发布时间】:2019-04-06 13:41:54
【问题描述】:

我想将两个正数相加,而不使用任何基本运算符,例如 + 进行加法。我已经解决了这个问题(在 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 

summary [8,6,...] 总和达到 10 时会发生什么尚未实现。

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

两个数相加

【问题讨论】:

  • 你的add 函数是Int -> Int -> Int 并且你的add'' 函数返回一个[Int]
  • 还有更简单的方法来做你想做的事情,特别是如果数字总是正数,这意味着它们可以代表某物的长度......
  • 所以+ 是基本的,但moddiv 不是? :)(开玩笑)。 -- 很好的练习!
  • 您意识到succ :: Int -> Int 只是定义为succ x = x + 1,对吧?您正在跳过多个抽象层以避免+,它无论如何都隐藏在最后一层中。
  • 你也可以明确定义自己的succ'函数(succ 0 = 1; succ 1 = 2; -- etc),然后弄清楚如何处理携带。

标签: haskell addition


【解决方案1】:
  1. 您不能在元组上使用 headlast。 ...坦率地说,您应该从不使用这些函数,因为它们不安全(部分),但它们可以在列表中使用。在 Haskell 中,列表与元组完全不同。
    要获取元组的元素,请使用模式匹配

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

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

  2. 明确哪些函数是柯里化的,哪些不是。你写add'''的方式,它的类型签名实际上是Int -> Int -> Int。这与(Int, Int) -> Int等价,但与类型检查器仍然不一样。

  3. add'' 的结果是[Int],但您试图在add 的结果中将其用作Int。那不行,你需要再把数字转成数字。

  4. add'' 不处理空箱。这很容易解决,但比使用标准组合器更好。在你的情况下,这应该只在元素方面起作用,所以你可以简单地使用map——或者在压缩中直接使用zipWith。然后你也不需要解开任何元组,因为它可以与柯里化函数一起使用。


您尝试的干净版本:

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-overflow,但这只是因为在 fromDigits 中使用 + 的作弊。

【讨论】:

  • 完全没有用。 :) --- 9+9=18; 9+9+1=19。进位只有 0 或 1。偶尔需要额外的 succ。对吗?
【解决方案2】:

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!

现在剩下的大问题是如何处理那些 可怕的 10 岁及以上的数字。这里有一个想法:产生一个数字和一个进位

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

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

【讨论】:

    【解决方案3】:

    其他答案涵盖了您的方法中出了什么问题。但是,从理论的角度来看,它们都有一些缺点:它们要么将您置于[Int] 而不是Int,要么在从[Int]Int 的转换中使用(+)。更重要的是,他们在定义加法时使用moddiv 作为子例程——这没关系,但从理论上讲,你需要确保你可以定义moddiv 自己而不使用作为子程序添加!

    既然你说效率无关紧要,我建议使用数学家给出的通常的加法定义,即:0 + y = y,和 (x+1) + y = (x + y)+1。在这里,您应该将+1 视为一种单独的操作,而不是加法,一种更原始​​的操作:仅增加一个数字的操作。我们在 Haskell 中拼写为succ(它的“逆”为pred)。考虑到这个理论定义,Haskell 几乎是这样写的:

    add :: Int -> Int -> Int
    add 0 y = y
    add x y = succ (add (pred x) y)
    

    所以:与其他答案相比,我们可以接受 Int 并返回 Int,我们使用的唯一子程序是“感觉”更原始的子程序:succpred,并检查是否一个数字是零或非零。 (而且我们只用了三行短代码……大约是建议的最短替代方案的三分之一。)当然,我们付出的代价是性能很差……试试add (2^32) 0

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

    【讨论】:

      【解决方案4】:

      我的教授给出的官方回答

      也适用于正数和负数,但仍然要求两个数的长度相同

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

      【讨论】:

      • 这不需要“相同的长度”。实际上,它没有“长度”的概念,或者更确切地说,“长度”是值,如 unary... 它只是非常效率低下,你原来的方法要快得多。跨度>
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-12
      • 2016-02-22
      • 2011-05-26
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多