【问题标题】:Haskell get a filtered List of integersHaskell 得到一个过滤的整数列表
【发布时间】:2012-04-17 07:08:52
【问题描述】:

场景: 如果有一个整数数组,我想得到整数数组作为回报,它们的总数不应超过 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]

【问题讨论】:

  • 请指定“整数数组”。连续的?允许打孔吗?独特的解决方案?最佳解决方案(至于“最佳”的意思)?
  • 例如,[4,6]getUpTo numbers 的可接受答案吗?为什么,或者为什么不?
  • 我不确定你的问题是什么对不起:(
  • @Randy:您是在解决背包问题,还是该函数只需要计算满足您的约束的第一个解决方案(无论它是什么)?
  • 我认为您的意思是“列表”,而不是“数组”。在 Haskell 中它们是完全不同的,通常不使用数组。

标签: list haskell filter


【解决方案1】:

注意:这不是解决背包问题的方法:)

我想出的一个非常快速的解决方案是以下一个。当然,解决完整的背包问题会更难,但如果你只需要一个快速的解决方案,这应该可行:

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

通过在其他所有内容之前对数组进行排序,我可以一个接一个地从顶部取出项目,直到超过最大值;然后返回建立到该点的列表。

编辑:作为旁注,我交换了前两个参数的顺序,因为这种方式对于部分应用程序应该更有用。

【讨论】:

  • 正是我想要的。好吧,我确实明白这不是背包的解决方案,这只是我为了解问题并学习如何解决问题所做的一个小测试。非常感谢!
【解决方案2】:

出于教育目的(而且因为我想解释一些东西:-),这里有一个不同的版本,它使用了更多的标准函数。正如所写的那样,它比较慢,因为它计算了一些总和,并且不保持运行总计。另一方面,我认为它很好地表达了如何分解问题。

getUpTo :: [Int] -> [Int]
getUpTo = last . filter (\xs -> sum xs <= 10) . Data.List.inits

我已将解决方案编写为函数的“管道”;如果将getUpTo 应用于数字列表,Data.List.inits 首先应用于列表,然后filter (\xs -&gt; sum xs &lt;= 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 -&gt; sum xs &lt;= 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 -&gt; sum xs &lt;= 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 -&gt; sum xs == 10) . Data.List.inits。或者,如果您想查看尾段,请使用head . filter (\xs -&gt; sum xs &lt;= 10) . Data.List.tails;或者考虑所有可能的子列表(即低效的背包解决方案!):last . filter (\xs -&gt; sum xs &lt;= 10) . Data.List.sortBy (\xs ys -&gt; length xscomparelength ys) . Control.Monad.filterM (const [False,True]) - 但我不打算在这里解释,我已经漫无边际了!

【讨论】:

  • 提高效率的一种简单方法是使用scanl1 (+)zip 在使用takeWhile 之前使用原始列表获取部分总和:getUpTo xs = map fst . takeWhile ((&lt;= 10) . snd) . zip xs $ scanl1 (+) xs
  • @hammar:我也想过解释类似的事情(但更笨拙,压缩scanl (+) 0 xsinits xs)但我想保持基本。相反,我选择展示我最喜欢的 Haskell 魔法(使用filterM 生成所有子列表)。嗯,我的版本或多或少是无意义的:-)
【解决方案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

【讨论】:

  • 您好,这会返回一个空列表作为结果。不太清楚为什么:(
  • @Randy 您是否使用了getUpTo numbers 0,就像您的问题说您想用作输入一样? ;-)
【解决方案4】:

我对 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]

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-27
    • 1970-01-01
    • 2023-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多