出于教育目的(并且因为我想解释一些东西:-),这里有一个使用更多标准函数的不同版本。由于它计算了许多总和,而没有保持一个运行总数,所以速度较慢。另一方面,我认为它很好地表达了如何分解问题。
getUpTo :: [Int] -> [Int]
getUpTo = last . filter (\xs -> sum xs <= 10) . Data.List.inits
我将解决方案编写为函数“pipeline”;如果您将
getUpTo
应用于数字列表,则首先对列表应用
Data.List.inits
,然后对结果应用
filter (\xs -> sum xs <= 10)
,最后对该结果的
最后一个应用
last
。
现在,让我们看看这三个函数各自的作用。首先,
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
,如果
xs
的总和小于10,则返回
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]
不是,因此应用于
[2,3,4,5,6]
的
filter (\xs -> sum xs <= 10) . Data.List.inits
的结果是
[[],[2],[2,3],[2,3,4]]
,同样是一个整数列表的列表。
最后一步最容易:我们只需返回整数列表的列表的最后一个元素。从原则上讲,这是不安全的,因为空列表的最后一个元素应该是什么?在我们的情况下,我们可以继续进行,因为inits始终首先返回空列表[],其总和为0,小于10,因此我们正在取最后一个元素的列表中至少有一个元素。我们将last应用于包含原始列表的初始段的列表,这些初始段的总和小于10且按长度排序。换句话说:我们返回总和小于10的最长初始段-这就是您想要的!
如果您的数字列表中有负数,则按此方式操作可能会返回您不期望的内容:getUpTo [10,4,-5,20]返回[10,4,-5],因为这是[10,4,-5,20]的最长初始段,其总和低于10;即使[10,4]超过了10。如果这不是您想要的行为,并且期望[10],则必须使用takeWhile替换filter-这实际上会在遇到谓词返回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 xscompare
length ys) . Control.Monad.filterM (const [False,True]) -但我不打算在这里解释那个。
[4,6]
是一个可接受的答案吗?为什么或为什么不是? - dave4420