我想将两个正数加在一起,而不使用+之类的任何基本运算符。我已经解决了这个问题(在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)
将两个数字加在一起
答案 0 :(得分:3)
您不能在元组上使用head
和last
。 ...坦率地说,您不应该完全使用这些功能,因为它们不安全(部分),但是它们可以在列表中使用。在Haskell中,列表与元组完全不同。
要使用元组的元素,请使用模式匹配。
add'' ((x,y):xs) = [add''' x y] ++ add'' xs
(要获得列表中的元素,模式匹配通常也是最好的选择。)或者,您可以使用fst
和snd
,它们在2元组上执行您显然认为的操作head
和last
。
要明确哪些函数是咖喱的,哪些不是。编写add'''
的方式,其类型签名实际上是Int -> Int -> Int
。与(Int, Int) -> Int
等价(em),但与类型检查器仍不相同。
add''
的结果为[Int]
,但是您正在尝试将Int
的结果用作add
。那行不通,您需要再次将数字转换为数字。
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)
head
和last
的意思是fst
和snd
,但您根本不需要它们,组件就在那里:
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
。而且,他们在定义加法时将div
和mod
用作子例程-可以,但是从理论上讲,您需要确保可以定义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可以拉。