Haskell右侧递归到左侧递归

时间:2014-05-24 14:47:47

标签: list haskell optimization recursion selector

在我的问题中我有列表列表,我想找到列表列表是选择器(选择器 - 列表中只包含每个列表中的一个元素),满足特殊条件。

生成所有选择器的代码如下所示:

selectors :: [[a]] -> [[a]]
selectors [] = [[]]
selectors (y:ys) = [ (x:xs) | x <- y, xs <- selectors ys]

如果我想添加一些额外条件,那就像

selectors :: [[a]] -> ([a] -> Bool) -> [[a]]
selectors [] _ = [[]]
selectors (y:ys) f = [ (x:xs) | x <- y, xs <- selectors ys f, f xs]

然而在我的问题中,我需要条件可靠的元素是列表的候选元素,以及我当前构建的列表中的内容。所以这就像是:

selectors :: [[a]] -> ( a-> [a] -> Bool) -> [[a]]
selectors [] _ = [[]]
selectors (y:ys) f = [ (x:xs) | x <- y, xs <- selectors ys f, f x xs]

这工作非常慢,因为最初递归非常深入,真正的工作从那里开始,而如果构建列表从左边开始,这将会更快,所以每当我尝试向我添加新元素时列表,我知道这不能添加所以我只是尝试添加新元素。我怎样才能以这种方式完成这项工作?

1 个答案:

答案 0 :(得分:3)

您可以通过转换循环体来更改某些搜索的顺序。

for i in foo                foo j in bar
  for j in bar     versus     foo i in foo
    do(i, j)                    do(i, j)

在列表理解语法中可以实现相同的效果。对于给定的示例,它可能是

[ (x:xs) | x <- y, xs <- selectors ys f, f x xs ]
-- versus
[ (x:xs) | xs <- selectors ys f, x <- y, f x xs ]

如果我们只将结果视为一组值(即顺序不重要),那么值是相同的。作为一个集合,考虑列表推导条款顺序的唯一规则是引用的变量必须绑定在其引用站点左边的子句中。

让我们稍微看一下这个符号,看看这些机制在更高保真度下工作。


列表推导(几乎)等同于do - 列表monad中的表示法。没有必要潜入monad是什么,我会声称我们的列表理解就像这样去了解

-- [ (x:xs) | x <- y, xs <- selectors ys f, f x xs ]
-- becomes...

do x  <- y
   xs <- selectors ys f
   guard (f x xs)
   return (x:xs)

翻译应该是显而易见的 - 每个包含(<-)的生成器子句都成为do - 语法绑定形式。每个保护子句使用(完全正常)函数do成为guard :: Bool -> [()] - 符号形式。最后,翻译保留了顺序。

但现在,do - 符号只是语法糖本身!它涉及到一系列功能应用程序。再说一次,为了不深入探讨monad的含义,我将完全按照这个转换进行。

-- [ (x:xs) | x <- y, xs <- selectors ys f, f x xs ]
-- becomes...

y >>= (\x -> selectors ys f >>= (\xs -> guard (f x xs) >> return (x:xs)))

特别是,x <- E之类的每个生成器行都变为E >>= (\x -> ...),其中...对应于do块的其余部分的转换。没有绑定箭头的E行转换为E >> ...。我们甚至可以通过注意E >> F只不过E >>= (\_ -> F)来进一步简化这一级别,以便我们最终拥有

y >>= (\x -> selectors ys f >>= (\xs -> guard (f x xs) >>= (\_ -> return (x:xs))))

最后一步,我们可以将(>>=)guardreturn函数转换为列表monad所采用的格式。特别是ls >>= f等于concat (map f ls)return x = [x]。实际上,在前缀而不是中缀形式中编写(>>=)也很方便,因此我们将其称为forl :: [a] -> (a -> [b]) -> [b]

函数guard有点奇怪。它看起来像guard b = if b then [()] else []。我们马上就会看到它是如何运作的。

forl y $ \x ->
  forl (selectors ys f) $ \xs ->
    forl (guard (f x xs)) $ \_ ->
      [x:xs]

现在这是一个完整的翻译。如果我们能够理解这一点,那么我们就已经理解了列表理解的机制。为了比较,当我们切换生成器子句的顺序

时,这就是列表理解的后果
forl y $ \x ->                             forl (selectors ys f) $ \xs ->
  forl (selectors ys f) $ \xs ->             forl y $ \x ->
    forl (guard (f x xs)) $ \_ ->              forl (guard (f x xs)) $ \_ ->
      [x:xs]                                     [x:xs]

看起来与开头给出的命令式示例非常相似。让我们证明它实际上是相同的。


首先,我们可以发送forl (guard (f x xs)) $ \_ -> [x:xs]的工作方式。我们只是内联guard然后forl

的定义
forl (if (f x xs) then [()] else []) (\_ -> [x:xs])
concat (map (\_ -> [x:xs]) (if (f x xs) then [()] else []))

我们可以将if从内部“抬起”,注意到一旦我们将整个物体包裹在外部升力中,(f x xs)的值在{{1}中都是固定的}和then分支。

else

最后,我们可以内联if (f x xs) then concat (map (\_ -> [x:xs]) [()] else concat (map (\_ -> [x:xs]) [] s然后map s

concat

现在应该越来越清楚这些“for”循环是如何工作的。它们遍历一个主体并产生一个结果列表。由于我们希望正文也是一个if f x xs then concat [(\_ -> [x:xs]) ()] then concat [] if f x xs then [x:xs] else [] forl y $ \x -> forl (selectors ys f) $ \xs -> forl (selectors ys f) $ \xs -> forl y $ \x -> if f x xs then [x:xs] else [] if f x xs then [x:xs] else [] 循环,我们必须预测正文中的值本身就是一个列表 - 我们使用forl展平了额外的列表层。