Reputation: 457
I am trying to solve this simple problem in an elegant and expressive way. Usually, I would start from the end of both lists, add the corresponding elements and store a carry to compute the next digits. However, I am struggling to solve this with recursion and without using the reverse
function.
This is my first attempt:
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
(where the add and add' functions perform the needed operations)
The resulting list appears to be correct, but in reverse order. I don't know how to proceed since I chose to build the result in the pair that is returned along with the carry in the auxiliary function (normally I would do something like s : binarySum'...
).
Also I feel like the code is too cluttered and not as elegant as it should be.
Any help is greatly appreciated!
Upvotes: 0
Views: 1931
Reputation: 71
Four years too late. I don't care though:
(+++) :: [Int] -> [Int] -> [Int]
(+++) n1 n2 = f2 $ foldr f1 ([],0) (copulate n1 n2)
where
-- immitates `zip`
-- but it will make sure that both lists are of same length.
-- if they are not of the same length, then the shorter list
-- will be adjusted accordingly.
copulate :: [Int] -> [Int] -> [(Int,Int)]
copulate n1 n2
| length n1 == length n2 = zip n1 n2
| otherwise =
let diff = abs (length n1 - length n2)
in if length n1 > length n2
then zip n1 (replicate diff 0 ++ n2)
else zip (replicate diff 0 ++ n1) n2
f1 :: (Int,Int) -> ([Int],Int) -> ([Int],Int)
f1 (x,y) (res,z)
| any (`notElem` [0,1])[x,y] = error "can only process binary bits"
| otherwise =
let (l,rest) = f3 x y z
in (l : res,rest)
--adding the rest if exists any
f2 :: ([Int],Int) -> [Int]
f2 (list,0) = list
f2 (list,1) = [1] ++ list
--doing the addition and calculating the rest
f3 :: Int -> Int -> Int -> (Int,Int)
f3 x y z = ((x+y+z) `mod` 2,if x+y+z>=2 then 1 else 0)
foldr
is great to traverse from the right side without reversing. Besides, the whole list is being processed, and the function is strict in strict in f1
which makes foldr
perfect for this operation.
Look at the accumulator of foldr
. It's in the beginning a tuple: ([],0).
now you can test it: [1,0,1] +++ [1,0,0] will give 1001 And thanks for the infix operator, you'll be able to chain more addition if you want to, like: [1,0,1] +++ [1,0,0] +++ [1,0,0,0,0,0,0]
Even though the answer looks lengthy, it was so easy to rationalize and to write, which makes it not lengthy in logic, and I find this solution easier than recursion.
Upvotes: 0
Reputation: 32309
You are almost there (at least your explanation seems to indicate so - your code relies on an add
function you haven't included). The trick is indeed to keep the carry as a separate number in a tuple in an auxiliary function (which I've named binarySum'
). The invariant you are working with is then that the list returned has the same length as the larger of the two lists provided (and is the first digits of their sum) - a carry, if there is any, is held separately.
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)
Upvotes: 2