【问题标题】:Haskell function that outputs all combinations within the input list that add to the input numberHaskell 函数输出输入列表中添加到输入数字的所有组合
【发布时间】:2016-07-17 08:54:58
【问题描述】:

我想在 haskell 中编写一个函数,该函数将整数列表和整数值作为输入,并输出所有列表的列表,其中包含的元素组合加起来为输入整数。

例如:

myFunc [3,7,5,9,13,17] 30 = [[13,17],[3,5,9,13]]

尝试:

myFunc :: [Integer] -> Integer -> [[Integer]]
myFunc list sm = case list of
    [] -> []
    [x]
        | x == sm -> [x]
        | otherwise -> []
    (x : xs)
        | x + myFunc xs == sm -> [x] ++ myFunc[xs]
        | otherwise -> myFunc xs

我的代码只产生一个组合,并且该组合必须是连续的,这不是我想要实现的目标

【问题讨论】:

  • 您说允许重复,但您没有将 [3,3,3,7,7,7] 作为示例中加起来为 30 的列表之一。如果这不是您所说的重复,那是什么意思
  • 我的意思是一个数字可以以两种组合出现,例如 13 在 [[13,17],[3,5,9,13]] 中出现两次。我想我不应该这么说,因为很明显我们可以在两种组合中使用一个数字,所以我会删除它。

标签: list haskell recursion


【解决方案1】:

另一种方法是使用右折叠:

fun :: (Foldable t, Num a, Eq a) => t a -> a -> [[a]]
fun = foldr go $ \a -> if a == 0 then [[]] else []
    where go x f a = f a ++ ((x:) <$> f (a - x))

那么,

\> fun [3,7,5,9,13,17] 30
[[13,17],[3,5,9,13]]

\> fun [3,7,5,9,13,17] 12
[[7,5],[3,9]]

这种方法的一个优点是它创建任何列表除非它加起来达到所需的值。 然而,基于过滤的方法将创建所有可能的子序列列表,只是在过滤步骤中删除其中的大部分。

【讨论】:

  • 我并不完全相信你的最后一句话。您确实没有创建任何[a] 列表,但您最终会创建一个像(a:) &lt;$&gt; (b:) &lt;$&gt; (c:) &lt;$&gt; ... 这样的thunk 链接列表,只有在发现... 毕竟是一个空列表时才会被丢弃。因此,正如您所说,虽然您没有创建任何列表,但您在这些 thunk 上花费的时间和内存似乎与在列表上花费的时间和内存一样多。
  • @amalloy 您可以对fun [1..24] 17 进行基准测试以亲自查看。使用 ghc --make -O2 filter ... subsequences 解决方案比右折叠方法(ghc 7.10.3)慢 3 倍
  • 看到它更快不能告诉我为什么它更快。在我看来,您似乎仍然为您可能会或可能不会做的每个缺点分配一个 thunk。
  • @amalloy 不,这不是真的!在 [1..24] 中,对于所有以 1 开头的子序列(其中很多是 2^23),我只创建 一个单一的 thunk
  • 啊哈!好的,我现在明白了。感谢您清除它。
【解决方案2】:

编写一个函数来创建所有子集

f [] = [[]]
f (x:xs) = f xs ++ map (x:) (f xs)

然后使用过滤器

filter ((==30) . sum) $ f [3,7,5,9,13,17]

[[13,17],[3,5,9,13]]

根据@Ingo 的建议,您可以在生成列表时对其进行修剪,例如

f :: (Num a, Ord a) => [a] -> [[a]]
f [] = [[]]
f (x:xs) = f xs ++ (filter ((<=30) . sum) $ map (x:) $ f xs)

应该比生成所有 2^N 个元素更快。

【讨论】:

  • 这将是编写更高效函数的良好基础,当结果列表已经大于目标结果时,该函数会切断子集构建过程。
【解决方案3】:

这是一个替代解决方案的想法:生成一个列表列表,总和为目标数,即:

[30]
[29,1]
[28,2]
[28,1,1]
...

然后才过滤可以从给定列表中构建的那些。

Pro:可能会快得多,特别是如果您的输入列表很长并且您的目标数量相对较小,这样加法列表比输入列表的子集列表要小得多。

Con:只有在游戏中没有 0 时才有效。

最后,您可以同时使用两种方法,并编写一个函数来决定在给定一些输入列表和目标数字的情况下哪种算法更快。

【讨论】:

    【解决方案4】:

    您可以使用 Data.List 中的 subsequences 为您提供所有可能的值组合,然后根据您的要求进行过滤,将它们添加到 30。

    myFunc :: [Integer] -> Integer -> [[Integer]]
    myFunc list sm =
      filter (\x -> sum x == sm) $ subsequences list
    

    【讨论】:

    • 但那是作弊:)。我想只使用原始递归来做到这一点。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-09
    • 2017-07-11
    • 1970-01-01
    相关资源
    最近更新 更多