如何在 Haskell 中使用拖车过滤器?

How to use tow filters in Haskell?

我想编写一个函数来打印所有已排序的子序列和另一个条件。

我已经实现了一个显示所有排序子序列的函数,如下所示:

allSubseqs:: Ord a => [a] -> [[a]]
allSubseqs =  filter' isSorted . subsequences  

我想进一步过滤 select 只有最大长度的列表。 我知道如何用 let 来实现,但我想直接将它添加到 allSubseqs 函数中:

这是代码的结果,它是正确的:

[[3,5,7,8],[1,5,7,8],[1,2,7,8]]

好吧,其中一个 let 可以直接离开;您的 let x2 = x1 并没有真正发挥作用,我们只需将 x2 替换为 x1 即可。

main :: IO ()
main = do
  let x1 = allSubseqs2 [6,3,1,5,2,7,8,1]
  print $ filter' ((==) (maximum (map' length x1)) . length) x1

从技术上讲,我们也可以用 allSubseqs2 [...] 替换所有出现的 x1,但不幸的是,这会计算两次子序列。所以let必须留下。

不过,如果需要的话,我们可以分解出对 allSubseqs2filter' 的调用,将它们组合在一个函数中。它看起来几乎相同,只是将 print 替换为...什么都没有!

longSubseqs values = do
  let x1 = allSubseqs2 values
  filter' ((==) (maximum (map' length x1)) . length) x1

其实这个有点骗人;大多数 Haskellers 会期望在看到 do 块时会发生一些 monadic 的事情,而这里没有发生任何这样的事情。让我们将 do/let 更改为 let/in:

longSubseqs values = let x1 = allSubseqs2 values in
  filter' ((==) (maximum (map' length x1)) . length) x1

使用任一定义,我们现在可以写出更短的 main:

main = print $ longSubseqs [6,3,1,5,2,7,8,1]
-- OR
main = do
  print $ longSubseqs [6,3,1,5,2,7,8,1]

听起来您正在尝试实现您的功能 point-free。也就是说,您希望将所有过滤器和列表操作定义为一系列函数应用程序,而不是使用变量和 let 绑定。这当然可行!

我喜欢的一个很好的方法是使用箭头组合器而不是 (.),因为它允许定义 left-to-right 管道 函数而不是典型的(数学)right-to-left 组合。我们将从导入开始:

import Control.Arrow ((>>>), (&&&))

>>> 运算符只是翻转组合,我们稍后会回到 &&&

现在,让我们把你的 allSubseqs 函数写成这种箭头样式:

allSubseqs:: Ord a => [a] -> [[a]]
allSubseqs = subsequences >>> filter' isSorted

接下来,我们要进行另一个过滤,这次是针对列表的长度,但我们还需要知道列表的最大长度。通常,这是使用 let 语句或变量绑定(就像您所做的那样)的地方,但我们可以使用 &&&&&& 组合器接受两个函数,每个函数接受相同的输入,并产生一对函数的输出。我们可以像这样用它代替 let

allSubseqsAndLongestLength :: Ord a => [a] -> (Int, [[a]])
allSubseqsAndLongestLength = subsequences 
    >>> filter' isSorted 
    >>> (maximum . map' length) &&& id

到目前为止的输出现在是一对已排序子序列的最大长度以及所有已排序子序列的列表。是时候将其通过管道传输到最终过滤器中了:

allLongestSubseqs :: Ord a => [a] -> [[a]]
allLongestSubseqs = subsequences 
    >>> filter' isSorted 
    >>> (maximum . map' length) &&& id
    >>> uncurry (filter' . (. length) . (==))

我们本可以将最后一行写成 >>> (\(maxLen, lst) -> filter' ((== maxLen) . length) lst),但我冒昧地把它写成 point-free 就像函数的其余部分一样。

给你!您甚至可以根据需要添加更多 filter 或其他列表操作,只需将它们组合到链的末尾即可。

使用“decorate-process-undecorate”范式a.k.a。 Schwartzian transform as in DDub's 在准备好要处理的数据后,您可以在链中添加额外的过滤器。

但是最好在这里完全避免第二个过滤器,方法是首先按顺序生成子列表,最长的第一个,然后只取头元素:

longestSorted :: Ord a => [a] -> [[a]]
longestSorted = rpowers >>>
   map (filter isSorted) >>>
   dropWhile null >>>
   concat . take 1

-- > longestSorted [1,4,3,5]
-- [[1,4,5],[1,3,5]]

这使用 rpowers 来自 :

-- longest first:
rpowers :: [a] -> [ [[a]]]
rpowers []     =  [ [[]] ]
rpowers (x:xs) = 
     [ map (x:) b ++ a |  (a:b:_) <- tails ([[]] ++ rpowers xs) ] 
      ++          [ [[]] ] 

-- shortest first, for comparison:
powers :: [a] -> [ [[a]]]
powers []     =  [ [[]] ]
powers (x:xs) =  [ [[]] ] ++
     [ map (x:) a ++ b |  (a:b:_) <- tails (powers xs ++ [[]]) ]