使用折叠的列表的局部最大值

时间:2014-02-26 15:54:01

标签: haskell

有没有办法找出使用foldrfoldl列表的本地最大值,或者我必须使用unfoldr,因为列表的第一个和最后一个元素不是本地的最大值?

我理解如何使用带有警卫的递归来找到它,例如

localMax :: [Int] -> [Int]
localMax []       = []
localMax (x:[])   = []
localMax (x:y:[]) = []
localMax (x:y:z:zs)
    | y > z && y > x = y:localMax (y:z:zs)
    | otherwise = localMax (y:z:zs)

8 个答案:

答案 0 :(得分:8)

你可以,但它非常难看。主要是因为要找到局部最大值,您需要知道上一个和下一个元素。

前面的元素没有问题,但是折叠不应该知道它后面的元素。

作为一种可能的方法

 import Data.List (zip3)

 localMax xs = map proj2
               . filter isLocalMax
               $ zip3 xs (tail xs) (drop 2 xs)
   where isLocalMax (x, y, z) = x < y && z < y
         proj2 (_,x,_) = x

所以我们将列表移动一到两个并将它们全部压缩,以便[a, b, c, d, e]成为。[(a, b, c), (b, c, d), (c, d, e)] map然后我们只使用filtermap来选择合适的元素。

请注意,filterfoldr可能会被打成一个{{1}},如果你真的想要的话。

答案 1 :(得分:3)

localMaxAsUnfold :: [Int] -> [Int]
localMaxAsUnfold = unfoldr work
  where
    work (x:y:z:rest)
      | y > x && y > z = Just (y, y:z:rest)
      | otherwise      = work (y:z:rest) -- cheat and recurse internally
    work _             = Nothing

localMaxAsFold :: [Int] -> [Int]
localMaxAsFold = foldr work [] . makeTriples
  where
    makeTriples :: [Int] -> [(Int, Int, Int)]
    makeTriples vals = zip3 vals (tail vals) (drop 2 vals)

    work (x,y,z) vals
      | y > x && y > z = y : vals
      | otherwise      = vals

答案 2 :(得分:3)

我们总是可以通过短视的 1 foldr over (init . tails)来模仿富有洞察力的paramorphism

import Data.List
import Control.Applicative

localMaxima :: Ord a => [a] -> [a]
localMaxima = foldr g [] . init . tails . (zip <*> tail) 
   where
     g ((a,b):(_,c):_) r | a<b && b>c = b:r
     g  _              r              =   r

在这种特殊情况下,init可以省略。 zip <*> tail(这只是\xs -> zip xs (tail xs)的简写)形成了输入列表中连续元素对的列表。

不,我不认为它特别“丑陋”。 2 :)

1 2 cf. this answer here
2 another answer here

答案 3 :(得分:2)

我会尝试只使用折叠,就像你问的那样。这样的事情怎么样?

lmax (x:y:xs) = third $ foldl f (x,y,[]) xs
  where
    third (_, _, x) = x
    f (x, y, ls) z = (y, z, if y > x && y > z then y:ls else ls)

这个想法是你在折叠中传递一个元组而不是结果列表。元组(三元组)将包含最后两个元素和结果列表。该函数评估三元组的第二个元素是否是局部最小值w.r.t.第一个元素(它的前身)和由fold(它的后继者)传递的当前元素。

ghci> lmax [1,3,2]
[3]
ghci> lmax [3,4,2,5,1,7,6,1,9]
[7,5,4]
ghci> lmax [1..10]
[]
ghci> lmax []
*** Exception: test.hs:(4,1)-(5,66): Non-exhaustive patterns in function lmax

无论如何,当输入列表太短时,应该很容易使用您喜欢的任何方法来返回空结果列表。

请注意,使用foldl时,每个新结果都会附加在顶部。因此,结果列表相反。如果您希望将这些结果与原始列表中的顺序相同,则可能需要再次撤消lmax的结果:lmax' = reverse . lmax

答案 4 :(得分:2)

理念与Will Ness建议的非常接近,但没有应用拉链和更短的时间:

lm a = foldr (\(x:y:z:_) a -> if y > z && y > x then y:a else a) [] 
             $ filter ((2<) . length) (tails a)

可读性仍然值得怀疑:)

更新根据Will Ness的建议,也可以使用列表理解:

lm a = [y | (x:y:z:_) <- tails a, x < y && y > z]

答案 5 :(得分:1)

这是另一个在列表上没有模式匹配并且仅使用fold:

g :: a -> (a -> Bool) -> (a -> Bool) -> [a]
g x p n | p x && n x = [x]
        | otherwise  = []

localMax :: Ord a => [a] -> [a]
localMax l = fr $ const False
  where (_, fr) = foldr worker (const False, \_ -> []) l
        worker x (p,f) = ((> x), \n -> g x p n ++ f (> x))

这是如何工作的?

基本思想是在累加器中使用一个函数,这样我们就可以将后面的元素“传回”到前一个阶段。所以我们传回一个类型为a -> Bool的函数,只有当下一个元素大于参数时才返回True。 (此函数在上面的代码中称为n next。我们在累加器中还有另一个类型a -> Bool的函数,如果前一个元素小于传递的值(p称为previous),则返回True。当两个函数都返回True时,我们有一个最大值。这是g检查的内容。

实施例

*Main> localMax [3,4,2,5,1,7,6,1,9]
[4,5,7]
*Main> localMax [1..10]
[]
*Main> localMax []
[]

答案 6 :(得分:1)

这是另一个带有zipWith的文件

localMax a = [ x | (x,v) <- zip a localMaxInd, v]
where  
   localMaxInd = zipWith (&&) u v
         where 
              u = False : zipWith (>) (tail a) a
              v = zipWith (>) a (tail a) 

测试用例

> localMax [3,4,2,5,1,7,6,1,9]
[4,5,7]

答案 7 :(得分:0)

我想@ jozefg的答案, 可以用折叠来表达整个事情。列表上的任何递归操作最终都可以使用foldr表示,但通常很难看。

首先让我们使用mapMaybezip来实现locMax,与@jozefg所做的非常相似::

import Prelude hiding (zip)

isMax :: (Ord a) => ((a, a), a) -> Maybe a
isMax ((x, y), z) | y > z && y > x   = Just y
                  | otherwise        = Nothing

locMax :: (Ord a) => [a] -> [a]
locMax xs@(_:xs'@(_:xs'')) = mapMaybe isMax $ zip (zip xs xs') xs''

使用mapMaybe实施foldr并不困难:

mapMaybe :: (a -> Maybe b) -> [a] -> [b]
mapMaybe f = foldr (\x xs -> maybe xs (: xs) (f x)) []

实现zip有点棘手,因为我们需要一次使用两个列表。我们要做的是,我们将在foldr内积累[b] -> [(a,b)]类型的函数,该函数将使用第二个列表。

基本情况很简单。如果第一个列表为空,则构造的函数为const [],因此无论第二个列表是什么,结果也都是空的。

折叠步骤采用值x : a,这是一个转换[b]子列表的累积函数。既然我们再次生成一个函数,我们只需要第三个[b]类型的参数。如果没有b s,则结果为空列表。如果至少有一个,我们构建一对并在其余f s上调用b

zip :: [a] -> [b] -> [(a,b)]
zip = foldr step (const [])
  where
    step _ _ []       = []
    step x f (y : ys) = (x, y) : f ys

您可以验证此实现是否具有所需的属性,并且如果一个或两个列表是无限的,也可以正常工作。