【问题标题】:How to sum elements of two lists. Haskell如何对两个列表的元素求和。哈斯克尔
【发布时间】:2021-01-10 11:37:58
【问题描述】:

我才刚刚开始学习 Haskell,目前我正在探索列表的可能性。 我想总结两个列表,但不知何故出错了。

所以:

输入:sumTwoLists [2,5,7,7,9] [1,2,2](基本上是 25779 + 122)

输出:[2,5,9,0,1]

首先,我把整个列表倒过来了,因为多位数字的加法必须从末尾开始:

reverseList :: [Int] -> [Int]
reverseList [] = []
reverseList (x:xs) = reverseList xs ++ [x]

它有效。然后我实现了一个add函数:

add :: (Num a) => [a] -> [a] -> [a]
add _ [] = []
add [] _ = []
add (x:xs) (y:ys) = (x + y) : add xs ys

但是当一个列表比另一个短时,它就会出错。 (添加 [2,5,7,7,9] [1,2,2] = [3,7,9]) 所以finction也必须在小数的末尾加0([1,2,2] = [1,2,2,0,0]。)

然后我尝试像这样实现 sumTwoLists 函数:

sumTwoLists :: [Int] -> [Int] -> [Int]
sumTwoLists (x:xs) (y:ys) = reverseList ((reverseList (x:xs)) add (reverseList (y:ys)))

但是这段代码没有考虑元素不能大于9的事实。 我不想将元素转换为 Int 或 Integer,这就是我不使用它们的原因。

我基本上只是想反转列表,然后在最短的列表中添加 0,然后将每个元素与另一个列表中的元素相加,如果结果>9,则结果除以 10(mod? ) 并且相邻的数量增加

如有任何帮助,我将不胜感激!

【问题讨论】:

  • 似乎您只需要能够将数字与其数字列表相互转换 - 然后您可以转换两个列表,添加结果数字,并将结果转换回列表。要编写这样的函数,您可以使用显式递归,或者使用showread“作弊”在数字和字符串之间进行转换,因为字符串是字符列表,在这种情况下,字符将是数字。跨度>
  • @RobinZigmond,我知道我可以做到,而且我之前已经做到了,但是现在我正在学习列表,我想学习如何直接在列表中进行操作,而不需要转换

标签: list haskell element add reverse


【解决方案1】:

用列表做这件事没有多大意义,因为它会引入很多额外的问题:

  1. 如果两个列表的长度不同,或者总和需要一个额外的元素,则用零填充;和
  2. 考虑到两个数字相加可能会产生大于9 的值,因此引入了进位

add 函数无法正常工作,因为它从列表之一用完的那一刻起停止,而且它没有考虑到数字可能“溢出”。因此我们应该构造一个函数add,它有一个额外的参数:进位:

add' :: Int -> [Int] -> [Int] -> [Int]
add' 0 [] [] = []
add' n [] [] = [n]
add' n xs [] = add' n xs [0]
add' n [] xs = add' n [0] xs
add' n (x:xs) (y:ys) = r : add' q xs ys
    where (q,r) = quotRem (x+y+n) 10

add 因此以零作为进位开始:

add :: [Int] -> [Int] -> [Int]
add = add' 0

如果我们这样计算反向列表的总和,我们得到:

Prelude> add [9,7,7,5,2] [2,2,1]
[1,0,9,5,2]

为了让这个工作,你需要使用add作为中缀运算符,两个操作数可以是空或非空列表:

sumTwoLists :: [Int] -> [Int] -> [Int]
sumTwoLists xs ys = reverseList ((reverseList xs) `add` (reverseList ys))

on :: (b -> b -> c) -> (a -> b) -> a -> a -> c:

import Data.Function(on)

sumTwoLists :: [Int] -> [Int] -> [Int]
sumTwoLists = add `on` reverse

对于给定的样本输入,我们现在得到:

Prelude> sumTwoLists [2,5,7,7,9] [1,2,2]
[2,5,9,0,1]

最后reverseList 已经存在于Preludereverse :: [a] -> [a]。你实现的reverseList函数在O(n2)中运行,效率不是很高,你可以使用累加器来获取线性时间。

【讨论】:

  • 你的第二个sumTwoLists 应该是sumTwoLists = add `on` reverse,否则很好回答:)
  • 我认为你删除了错误的xs ys 上的sumTwoLists 所以现在它们都坏了。
【解决方案2】:

这是以 10 为底的进数和。这是一个实现。

-- odometer (add one in base b)
odom :: Int -> [Int] -> [Int]
odom b (x : xs) | x<(b-1) = (x+1) : xs
                | xs==[] = [0,1]
                | otherwise = 0 : odom b xs

-- iterated odometer
odom_iter :: Int -> [Int] -> Int -> [Int]
odom_iter b t n | n == 0 = t
                | otherwise = odom b (odom_iter b t (n-1))

-- adic addition
sumadic :: Int -> [Int] -> [Int] -> [Int]
sumadic b (x:xs) (y:ys) | xs == [] = odom_iter b (y:ys) x
                        | ys == [] = odom_iter b (x:xs) y
                        | sumxy < b = (sumxy : (sumadic b xs ys))
                        | sumxy == b = (0 : (odom b (sumadic b xs ys)))
                        | otherwise = (1 : (odom b (sumadic b xs ys)))
                          where sumxy = x+y

这通过颠倒列表来工作:

> sumadic 10 [9, 7, 7, 5, 2] [2, 2, 1]
[1,0,9,5,2]

所以您只需将sumadic 应用于反转列表并反转输出:

sumTwoLists x y = reverse $ sumadic 10 (reverse x) (reverse y)

【讨论】:

    【解决方案3】:

    此答案将使用 Haskell 内置的 reverse 而不是您的 reverseList,尽管它们可以互换。

    让我们先来看看你的 add 函数:

    add :: (Num a) => [a] -> [a] -> [a]
    -- look at these next 2 lines specfically
    add _ [] = []
    add [] _ = []
    add (x:xs) (y:ys) = (x + y) : add xs ys
    

    如果您将一个非空列表添加到一个空列表,您当前的代码会显示它是一个空列表,这显然不是真的(add [1, 2, 3] [] 应该是 [1, 2, 3])。因此,您可以首先通过返回另一个列表来修复您的基本情况:

    add :: (Num a) => [a] -> [a] -> [a]
    -- return the other list
    add [] x = x
    add x [] = x
    add (x:xs) (y:ys) = (x + y) : add xs ys
    

    如果您正在添加两个空列表,那么另一个列表就是空列表,因此您仍然可以正确返回空列表。现在,我们可以解决“这段代码没有考虑元素不能大于9”的部分。由于您的加法方法是模拟您如何在笔和纸上进行加法,因此请继续这样做。例如,如果结果为 12,则数字为 2,并且携带 1。请记住,mod 是除法的余数,所以12 `mod` 10 (backticks in Haskell makes a function infix) 是2,而12 `div` 10,由于整数除法的性质向下舍入,会给你第一个数字之后的每个数字,即1.

    废话不多说,让我们写一些代码:

    -- change Num to Integral because we need to work with integers
    add :: (Integral a) => [a] -> [a] -> a -> [a]
    --        we need to add a carry now ^
    -- these base cases break down if carry is non-zero
    add [] x c
        -- if carry is zero we're fine
        | c == 0    = x
        -- just add the carry in as a digit
        | otherwise = add [c] x 0
    -- same applies here
    add x [] c
        | c == 0    = x
        | otherwise = add x [c] 0
    add (x:xs) (y:ys) c = dig : add xs ys rst
        where sum = x + y + c    -- find the sum of the digits plus the carry
              -- these two lines can also be written as (rst, dig) = sum `divMod` 10
              dig = sum `mod` 10 -- get the last digit
              rst = sum `div` 10 -- get the rest of the digits (the new carry)
    

    现在,您的辅助函数可以使用 0 作为初始进位来调用它:

    addTwoLists :: (Num a) => [a] -> [a] -> [a]
    addTwoLists x y = reverse $ add (reverse x) (reverse y) 0
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-04-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多