拆分列表并从子列表中进行求和?

时间:2012-03-19 17:39:26

标签: haskell numbers int

即时搜索我的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制作SUM的功能:

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

3 个答案:

答案 0 :(得分:6)

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

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

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

如果您小心,也可以滥用groupBy

第二步,看起来你几乎就在那里;你需要的最后一步是查看map :: (a -> b) -> ([a] -> [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的预测。
  • 预测groups(包括您的)的四个版本中的每个版本输出的输入,例如[][1,2,0,3,4,0],然后在ghci中测试您的预测。
  • 修改groups'''以展示其他实现之一的行为。
  • 修改groups'''以使用foldl代替foldr

答案 1 :(得分:2)

既然您已经完成了自己的问题,我会向您展示一个稍微冗长的版本。 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]
  11. 你有它!

    foldl版本。与上面类似,但产生一个反向列表,因此在此函数的开头使用reverse来反转列表。

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

    *从右侧折叠列表允许自然使用cons(:)函数,使用带左折叠的方法生成反转列表。 (可能有一种更简单的方法来做左边折叠版本,我没想到这会消除这种无关紧要。)

答案 2 :(得分:1)

正如您已经解决的那样,另一个版本:

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

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