方案: 如果有一个整数数组,我想获得整数数组作为回报,它们的总数不应超过10。
我是Haskell的初学者并在下面尝试过。如果有人能够纠正我,我将不胜感激。
numbers :: [Int]
numbers = [1,2,3,4,5,6,7,8,9,10, 11, 12]
getUpTo :: [Int] -> Int -> [Int]
getUpTo (x:xs) max =
if max <= 10
then
max = max + x
getUpTo xs max
else
x
输入
getUpTo numbers 0
预期输出
[1,2,3,4]
答案 0 :(得分:4)
当心:这不是背包问题的解决方案:)
我想出的一个非常快速的解决方案是以下一个。当然解决完整的背包问题会更难,但如果你只需要一个快速的解决方案,这应该有效:
import Data.List (sort)
getUpTo :: Int -> [Int] -> [Int]
getUpTo max xs = go (sort xs) 0 []
where
go [] sum acc = acc
go (x:xs) sum acc
| x + sum <= max = go xs (x + sum) (x:acc)
| otherwise = acc
通过在其他所有内容之前对数组进行排序,我可以从顶部逐个取出项目,直到达到最大值;然后返回构建到该点的列表。
编辑:作为旁注,我交换了前两个参数的顺序,因为这种方式应该对部分应用程序更有用。
答案 1 :(得分:4)
出于教育目的(因为我想解释一下:-),这里有一个不同的版本,它使用更多的标准功能。由于它写的速度较慢,因为它计算了一些总和,并且没有保持总计。另一方面,我认为它很好地表达了如何解决问题。
getUpTo :: [Int] -> [Int]
getUpTo = last . filter (\xs -> sum xs <= 10) . Data.List.inits
我把解决方案写成了一个功能的“管道”;如果您将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
的总和小于10,则列表True
返回xs
。这可能有点令人困惑,我想这是一个简单谓词的例子。 filter even [1,2,3,4,5,6,7]
返回[2,4,6]
,因为这是原始列表中的偶数值。在前面的示例中,列表[]
,[2]
,[2,3]
和[2,3,4]
的总和都小于10,但[2,3,4,5]
和{{1} }请不要,因此[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 - 所以列表中始终至少有一个元素列出我们正在采取的最后一个元素。我们将[]
应用于一个列表,该列表包含原始列表的初始段,总和小于10,按长度排序。换句话说:我们返回最长的初始段,总和小于10 - 这就是你想要的!
如果您的last
列表中有负数,这种做法可以返回您不期望的内容:numbers
返回getUpTo [10,4,-5,20]
,因为这是最长的一段[10,4,-5]
总和低于10;即使[10,4,-5,20]
高于10,如果这不是您想要的行为,并且期望[10,4]
,那么您必须将[10]
替换为filter
- 这实际上会将过滤停止为很快就会遇到谓词返回takeWhile
的第一个元素。例如。 False
评估为takeWhile [2,4,1,3,6,8,5,7]
。因此,在我们的情况下,使用[2,4]
会停止总和超过10的时刻,而不会尝试更长的段。
通过将takeWhile
编写为函数组合,可以轻松更改算法的各个部分:如果您希望最长的初始段精确到10,则可以使用getUpTo
。或者,如果您想要查看尾部,请使用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 xs
- 但我不打算在这里解释,我已经漫游了足够长的时间! / p>
答案 2 :(得分:3)
有快速版本的答案;但是,我认为查看代码所需的最小更改以使其按预期方式工作可能也是有益的。
numbers :: [Int]
numbers = [1,2,3,4,5,6,7,8,9,10, 11, 12]
getUpTo :: [Int] -> Int -> [Int]
getUpTo (x:xs) max =
if max < 10 -- (<), not (<=)
then
-- return a list that still contains x;
-- can't reassign to max, but can send a
-- different value on to the next
-- iteration of getUpTo
x : getUpTo xs (max + x)
else
[] -- don't want to return any more values here
答案 3 :(得分:0)
我是Haskell的新手。几个小时前我就开始使用它,因此我在每个问题中都看到了一个挑战,它帮助我摆脱了必要的思维方式,并有机会练习我的递归思维:)
我对这个问题有所思考,我想出了这个,也许是天真的解决方案:
upToBound :: (Integral a) => [a] -> a -> [a]
upToBound (x:xs) bound =
let
summation _ [] = []
summation n (m:ms)
| n + m <= bound = m:summation (n + m) ms
| otherwise = []
in
summation 0 (x:xs)
我知道已经有了更好的答案,我只是为了它的乐趣而做到了。
我的印象是我更改了原始调用的签名,因为我认为向外部函数调用提供初始零是没有意义的,因为我只能假设它一开始只能为零。因此,在我的实现中,我隐藏了来自调用者的种子,而是提供了最大限度,更有可能发生变化。
upToBound [1,2,3,4,5,6,7,8,9,0] 10
哪些输出:[1,2,3,4]