【问题标题】:Split list and make sum from sublist?拆分列表并从子列表中求和?
【发布时间】:2012-04-04 05:27:55
【问题描述】:

我正在为我的 Haskell 课程寻找解决方案。

我有一个数字列表,我需要为列表的每个部分返回 SUM。部分除以 0。我需要使用 FOLDL 函数。

示例:
初始列表:[1,2,3,0,3,4,0,5,2,1]
子列表 [[1,2,3],[3,4],[5,2,1]]
结果[6,7,7]

我有一个在初始列表中查找 0 的功能:

findPos list = [index+1 | (index, e) <- zip [0..] list, e == 0] 

(返回 [4,6] 示例中的初始列表)

和用 FOLDL 求和的函数:

sumList list = foldl (+) 0 list

但我完全没有把它放在一起:/

---- 我的解决方案
最后,我发现了与你们建议的完全不同的东西。
花了我一整天的时间:/

groups :: [Int] -> [Int]
groups list = [sum x | x <- makelist list]

makelist :: [Int] -> [[Int]]
makelist xs = reverse (foldl (\acc x -> zero x acc) [[]] xs)  

zero :: Int -> [[Int]] -> [[Int]]
zero x acc | x == 0 = addnewtolist acc
           | otherwise = addtolist x acc

addtolist :: Int -> [[Int]] -> [[Int]]
addtolist i listlist = (i : (head listlist)) : (drop 1 listlist)

addnewtolist :: [[Int]] -> [[Int]]
addnewtolist listlist = [] : listlist

【问题讨论】:

  • 我认为result 应该是[6,7,8] 而不是[6,7,7]

标签: haskell numbers int


【解决方案1】:

你已经解决了,另一个版本:

subListSums list = reverse $ foldl subSum [0] list where
   subSum xs 0 = 0 : xs
   subSum (x:xs) n = (x+n) : xs

(假设列表中只有非负数)

【讨论】:

  • 如果一个组的数字总和为零,则该零将被下一组“覆盖”,因此该总和不会出现在最终列表中。
  • 我想你可能误解了你自己的代码!您的代码中没有任何部分检查当前运行总和是否等于0;此外,在更经验的层面上,subListSums [2,-2,0,2,-2] 似乎在 ghci 中做了正确的事情。
【解决方案2】:

我会给你一些提示,而不是一个完整的解决方案,因为这听起来像是一个家庭作业。

我喜欢您建议的步骤细分。对于第一步(从带有零标记的数字列表到列表列表),我建议进行显式递归;试试这个模板:

splits []     = {- ... -}
splits (0:xs) = {- ... -}
splits (x:xs) = {- ... -}

如果你小心点,你也可以滥用groupBy

第二步,看起来你快到了;最后一步是查看map :: (a -&gt; b) -&gt; ([a] -&gt; [b]) 函数,它采用普通函数并在列表的每个元素上运行它。

作为一项额外的练习,您可能想考虑如何将整个事情一次性完成。这是可能的 - 甚至不是太难,如果您跟踪 foldr/foldl 的各种参数的类型必须是什么!

问题更改后的补充:

由于您似乎已经找到了解决方案,所以我现在很乐意提供一些剧透。 =)

我提出了两种可能的实现方式;一个按照您的建议一步一步进行,另一个一次完成。一步一步的可能如下所示:

splits []     = []
splits (0:xs) = [] : splits xs
splits (x:xs) = case splits xs of
    []       -> [[x]]
    (ys:yss) -> ((x:ys):yss)

groups' = map sum . splits

或者像这样:

splits' = groupBy (\x y -> y /= 0)
groups'' = map sum . splits'

一次性版本可能如下所示:

accumulate 0 xs     = 0:xs
accumulate n (x:xs) = (n+x):xs

groups''' = foldr accumulate [0]

为了检查你是否理解这些,这里有一些你可能想尝试的练习:

  • splitssplits'[1,2,3,0,4,5] 有什么作用? [1,2,0,3,4,0]? [0]? []?检查您在 ghci 中的预测。
  • 针对[][1,2,0,3,4,0] 等输入预测groups(包括您的)的四个版本中的每一个输出什么,然后在ghci 中测试您的预测。
  • 修改 groups''' 以展示其他实现之一的行为。
  • 修改groups''' 以使用foldl 而不是foldr

【讨论】:

  • 如果这不是家庭作业,您可以使用 Hackage 和 splitOn [0] 的拆分包。
  • 嗨,谢谢你的帮助 :) 遗憾的是它对我没有多大帮助。我客人说我需要“假人的例子”。我对haskell真的很陌生,所以我遇到了诸如“如何在列表中的第一个列表中添加一些东西”和“如何编写类型”等问题。无论如何,你能写出你建议的解决方案吗?我对它应该如何感兴趣:)
  • @DanielWagner 我喜欢你的最终版本使用辅助函数比我的更具可读性。很好的解决方案!
【解决方案3】:

现在您已经自己完成了这个问题,我将向您展示一个稍微不那么冗长的版本。在我看来,Foldr 似乎更好地解决了这个问题*,但是因为您要求foldl,所以我将向您展示我使用这两个函数的解决方案。

另外,您的示例似乎不正确,[5,2,1] 的总和是 8,而不是 7。

foldr 版本。

makelist' l = foldr (\x (n:ns) -> if x == 0 then 0:(n:ns) else (x + n):ns) [0] l

在这个版本中,我们遍历列表,如果当前元素 (x) 为 0,我们在累加器列表 (n:ns) 中添加一个新元素。否则,我们将当前元素的值加上累加器前面元素的值,并用这个值替换累加器前面的值。

一步一步:

  1. acc = [0], x = 1. Result is [0+1]
  2. acc = [1], x = 2. Result is [1+2]
  3. acc = [3], x = 5. Result is [3+5]
  4. acc = [8], x = 0. Result is 0:[8]
  5. acc = [0,8], x = 4. Result is [0+4,8]
  6. acc = [4,8], x = 3. Result is [4+3,8]
  7. acc = [7,8], x = 0. Result is 0:[7,8]
  8. acc = [0,7,8], x = 3. Result is [0+3,7,8]
  9. acc = [3,7,8], x = 2. Result is [3+2,7,8]
  10. acc = [5,7,8], x = 1. Result is [5+1,7,8] = [6,7,8]

你有它!

还有foldl 版本。工作原理与上面类似,但会产生一个反向列表,因此在此函数的开头使用 reverse 来取消反向列表。

makelist l = reverse $ foldl (\(n:ns) x -> if x == 0 then 0:(n:ns) else (x + n):ns) [0] l

*从右侧折叠列表可以自然地使用 cons (:) 函数,将我的方法与左折叠一起使用会产生一个反向列表。 (可能有一种更简单的方法来完成我没有想到的左折叠版本,它消除了这种琐碎性。)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-09-13
    • 2011-03-28
    • 1970-01-01
    • 2017-10-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多