出于教育目的(而且因为我想解释一些东西:-),这里有一个不同的版本,它使用了更多的标准函数。正如所写的那样,它比较慢,因为它计算了一些总和,并且不保持运行总计。另一方面,我认为它很好地表达了如何分解问题。
getUpTo :: [Int] -> [Int]
getUpTo = last . filter (\xs -> sum xs <= 10) . Data.List.inits
我已将解决方案编写为函数的“管道”;如果将getUpTo 应用于数字列表,Data.List.inits 首先应用于列表,然后filter (\xs -> sum xs <= 10) 应用于结果,最后last 应用于 that的结果>.
那么,让我们看看这三个函数各自的作用。首先,Data.List.inits 按长度递增的顺序返回列表的初始段。例如,Data.List.inits [2,3,4,5,6] 返回[[],[2],[2,3],[2,3,4],[2,3,4,5],[2,3,4,5,6]]。如您所见,这是一个整数列表。
接下来,filter (\xs -> sum xs <= 10) 按顺序遍历这些整数列表,如果它们的总和小于 10,则保留它们,否则丢弃它们。 filter 的第一个参数是一个谓词,如果 xs 的总和小于 10,则给定一个列表 xs 返回 True。起初这可能有点令人困惑,所以一个更简单的谓词示例是按顺序,我想。 filter even [1,2,3,4,5,6,7] 返回 [2,4,6] 因为这是原始列表中的偶数值。在前面的示例中,[]、[2]、[2,3] 和 [2,3,4] 列表的总和都小于 10,但 [2,3,4,5] 和 [2,3,4,5,6] 没有,所以 filter (\xs -> sum xs <= 10) . Data.List.inits 的结果应用于[2,3,4,5,6] 的是[[],[2],[2,3],[2,3,4]],同样是整数列表。
最后一步是最简单的:我们只返回整数列表的最后一个元素。这原则上是不安全的,因为空列表的最后一个元素应该是什么?在我们的例子中,我们很高兴,因为 inits 总是首先返回空列表 [],它的总和为 0,小于 10 - 所以我们的列表列表中总是至少有一个元素取最后一个元素。我们将last 应用于一个列表,该列表包含原始列表的初始段,这些段的总和小于 10,按长度排序。换句话说:我们返回总和小于 10 的最长初始段 - 这就是您想要的!
如果您的numbers 列表中有负数,这种处理方式可能会返回您不期望的结果:getUpTo [10,4,-5,20] 返回[10,4,-5],因为这是[10,4,-5,20] 的最长初始段,总和为10岁以下;即使 [10,4] 高于 10。如果这不是您想要的行为,并且期望 [10],那么您必须将 filter 替换为 takeWhile - 这基本上会在第一个元素为遇到谓词返回False。例如。 takeWhile [2,4,1,3,6,8,5,7] 的计算结果为 [2,4]。所以在我们的例子中,使用takeWhile 会在总和超过 10 时停止,而不是尝试更长的段。
通过将getUpTo 编写为函数的组合,更改算法的某些部分变得很容易:如果您想要总和正好为10 的最长初始段,您可以使用last . filter (\xs -> sum xs == 10) . Data.List.inits。或者,如果您想查看尾段,请使用head . filter (\xs -> sum xs <= 10) . Data.List.tails;或者考虑所有可能的子列表(即低效的背包解决方案!):last . filter (\xs -> sum xs <= 10) . Data.List.sortBy (\xs ys -> length xscomparelength ys) . Control.Monad.filterM (const [False,True]) - 但我不打算在这里解释,我已经漫无边际了!