【问题标题】:Building an exponentially sized list in haskell在 haskell 中构建一个指数大小的列表
【发布时间】:2019-06-16 03:36:56
【问题描述】:

我有两个函数,它们仅在 C 是特定模式时才起作用。 每个函数输出一个 C 列表。

我的目标是,在给定 [C] 的情况下,我希望在列表中调用 f1 和 f2 的所有可能性,而其余的保持不变。例如:

假设 C 的列表是:

    c1
    c2 --matches the pattern
    c3

那么我想要一个包含两个列表的列表

[[c1] ++ (f1 c2) ++ [c3],[c1] ++ (f2 c2) ++ [c3]]

但是,如果我有

c1
c2 --matches the pattern
c3 --matches the pattern

那么我们应该有 4 个列表,因为我们想要调用 f1 和 f2 的所有组合。 所以它会是:

[(f1 c1) ++ (f1 c2) ++ [c3], (f2 c1) ++ (f2 c2) ++ [c3], 
(f1 c1) ++ (f2 c2) ++ [c3], (f2 c1) ++ (f1 c2) ++ [c3]]

目前,我的代码结构大致如下:

f1 :: C -> [C]

f2 :: C ->  [C]

combine ::  [C] -> [[C]]
combine  my_pattern:xs =  ?
combine (x:xs) =  ?
combine [] = []

  where first_set = (f1 my_pattern)
        second_set = (f2 my_pattern)

有人可以就我如何填写剩余部分给出直觉吗? Data.List 中是否有任何有用的功能?我查看了文档,但无法立即注意到哪一个可能有用。

【问题讨论】:

  • 对我来说听起来像 traverse(在列表中)。
  • 如果x 匹配该模式,您的可能值为[f1 x, f2 x];如果不是,他们是[x]。您可以计算列表[[c1], [f1 c2, f2 c2], [c3]](例如,在您的第一个示例中,只有c2 匹配)?你能清楚地说明这里还有什么工作要做吗?

标签: list haskell functional-programming pattern-matching


【解决方案1】:

其他答案对我来说似乎很复杂。在这个答案中,我将扩展我的评论:这只是一个 foldMap 将非确定性单子(列表!)与序列类(列表!)结合起来。

首先写一个对列表的单个元素起作用的东西:

singleElement x
    | matchesThePattern x = [f1 x, f2 x]
    | otherwise = [[x]]

然后将其应用于每个元素:

import Data.Monoid
combine = foldMap (Ap . singleElement)

就是这样。这就是全部代码。

例如,假设我们希望每个字母重复 2 或 3 次,即 x -> xxxxx,并且所有其他字符都保持不变。

singleElement x
    | 'a' <= x && x <= 'z' = [[x, x], [x, x, x]]
    | otherwise = [[x]]

那我们可以在ghci中试一下:

> combine "123def"
Ap {getAp = ["123ddeeff","123ddeefff","123ddeeeff","123ddeeefff","123dddeeff","123dddeefff","123dddeeeff","123dddeeefff"]}

当然,在您自己的代码中选择一个比 singleElement 更好的名称。

【讨论】:

  • 我很犹豫是否要这么多地用 cmets 来打扰你,但我认为这是输入错误的。 Q 说f1, f2 :: C -&gt; [C],而不是C -&gt; C。 (刚刚注意到这个答案:))
  • @WillNess 真奇怪。那么我想知道它们之间的区别是什么。无论如何,这很容易处理:只需f1 x ++ f2 x 而不是[f1 x, f2 x]。但是为什么有两个函数而不是一个返回更多答案的函数呢?令人费解。 (我在您的回答中看到您将它们视为在不同条件下匹配 - 但问题实际上并没有...)
  • 这就是我对问题中给出的例子的解释。
  • @WillNess 好的,再次查看问题我想我现在了解f1f2 之间的区别。我已经更新了我的答案,并包含了一个更新的示例。
【解决方案2】:

你必须有

applicable_f1 :: C -> Bool
applicable_f2 :: C -> Bool

以某种方式定义。那么,

combinations :: [C] -> [[C]]
combinations cs = map concat . sequence $
    [ concat $ [ [ [c]   | not (applicable_f1 c || applicable_f2 c)]
               , [ f1 c  | applicable_f1 c]
               , [ f2 c  | applicable_f2 c] ]
      | c <- cs]

【讨论】:

  • 这几乎行得通!除非某些内容与模式不匹配,否则整个内容会返回一个空列表,而不是将元素添加到每个子列表中。也许我可以再做一次迭代并将缺少的东西添加到每个子列表中?虽然看起来不太优雅..
  • 这会发生在例如applicable_f1 返回True 所以f1 被应用,如果f1 c 然后返回[] 那么这就是它。如果您不希望这样,请注意applicable_f1 在这种情况下返回False。哦等等,也许那里有一个错误......会考虑一下......我只是想避免所有这些列表和头部的显式if then else,但也许这是不可避免的。
  • 我确实必须先过滤所有适用的内容,然后您的解决方案然后重新应用缺少的 c :)
  • 我想我修复了它现在。 :)
【解决方案3】:

我的方法是

  1. 为您当前查看的列表中的元素解决问题(xmy_pattern)。这意味着生成一个或多个新列表。
  2. 解决列表其余部分的问题 (xs)。这将为您返回一个列表列表 ([[C]])。
  3. 结合这两种解决方案。如果您从第 1 步生成了多个列表,则这些列表中的每一个 ([C]) 都将与第 2 步中的列表 ([[C]]) 中的每个列表(也称为 [C])合并。

我有两种可能的方法。

我不清楚您需要多少帮助,所以我的回答有点“不剧透”。如果需要,请要求澄清或更多详细信息。

列表理解

无需深入研究 ApplicativeTraversable 类型类的杂草,您就可以通过列表理解完成您想要的。

让我们考虑您的模式匹配的情况。我会写一个列表理解如下:

[ x ++ y | x <- _, y <- _] :: [[C]]
-- this means
-- x :: [C]
-- y :: [C]
-- _ :: [[C]]

这个列表推导式创建一个列表列表。 x 是前置的,所以它来自函数f1f2 的应用是有意义的。 y 是每个结果列表的尾部。我会让你弄清楚它可能是什么。

不匹配的情况比这个简单,可以这样写

[ x : y | y <- _] :: [[C]]
-- note that x is not local to the list comprehension
-- y :: [C]
-- _ :: [[C]]

虽然这真的只是上述列表推导的一个特例。

适用

解决此问题的另一种方法是使用Applicative[a] 实例。

让我们查看Applicative 实例列表下的函数(&lt;*&gt;)

-- this is the type when specialized to lists
(<*>) :: [a -> b] -> [a] -> [b]

这个函数有一种奇怪的类型签名。它需要一个函数列表和一个列表,然后返回另一个列表。它的作用是将每个函数a -&gt; b按顺序应用到[a]的每个元素上。

>>> [(+1), (+2)] <*> [1,2,3]
-- [2,3,4] comes from (+1)
-- [3,4,5] comes from (+2)
[2,3,4,3,4,5]

我们想退出[[C]],而不是[C],所以如果我们想使用(&lt;*&gt;),我们可以将其类型更多地专门化为

(<*>) :: [a -> [C]] -> [a] -> [[C]]

为避免混淆,我建议选择a = [C],它给出了

(<*>) :: [[C] -> [C]] -> [[C]] -> [[C]]

您的函数列表应该将正确的元素添加到您生成的列表中。第二个参数应该是递归调用返回的列表。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-02
    • 2012-05-29
    相关资源
    最近更新 更多