根据Haskell列表中n的条件删除元素n-1,n和n + 1

时间:2014-01-17 09:26:52

标签: list haskell filter

所以说我有一个从220的所有整数的列表。

[2 .. 20]

我想使用函数f x过滤列表(或者它是谓词?我对Haskell编程中使用的所有术语都不太习惯)。如果位置n的元素等于此函数f的true,我想删除位置n-1nn+1的元素。

实施例: 让我们说列表[2 .. 20]中位置4的元素(等于6)等于函数f的真值。然后,我想删除位置345的元素,它们分别等于567。 所以我的最终名单如下:

[2,3,4,8,9,10,11,12,13,14,15,16,17,18,19,20]

我是一名经验不足的Haskell程序员,只是为了好玩而玩。我曾考虑使用lambda函数作为谓词,但我不太清楚如何去做。我还想过使用像remove xs ys这样的函数删除xs中同时也是ys元素的所有元素,但我也不确定如何做到这一点。

任何帮助将不胜感激!

编辑:我意识到删除两个相邻元素是错误的,以便产生我想要的结果。此外,最好只将受影响元素的值(位置nn-1)更改为0,或者以其他方式标记/标记它们,而不是完全删除它们。原因是我想保持"删除"元素直到列表不再具有任何符合谓词的元素(及其前面的元素)。我只想"删除"他们来自原始列表。 由于我的方法从最初的问题变化很大,我将发布一个新的方法以反映我的新方法。我想感谢你们的所有回复,我从你的答案中学到了很多东西。谢谢!

编辑2:这是我的新问题:Remove elements at positions n and n-1 in a Haskell list, when n fits a predicate

6 个答案:

答案 0 :(得分:3)

您可以对多个元素进行模式匹配,并将过滤器应用于中间元素。

eitherside :: (Int->Bool) -> [Int] -> [Int]
eitherside f (i1:i2:i3:is) = if (f i2) 
    then eitherside f is 
    else i1 : (eitherside f (i2:i3:is))
eitherside f is = is
*Main> eitherside (==4) [1..10]
[1,2,6,7,8,9,10]
*Main> eitherside (==5) [1..10]
[1,2,3,7,8,9,10]
*Main> eitherside (==6) [1..10]
[1,2,3,4,8,9,10]

不喜欢(我原来的帖子):

eitherside :: (Int->Bool) -> [Int] -> [Int]
eitherside f (i1:i2:i3:is) = if (f i2) 
    then eitherside f is 
    else [i1,i2,i3] ++ (eitherside f is)
eitherside f is = is
*Main> eitherside (==5) [1..10]
[1,2,3,7,8,9,10]

这个糟糕的一个正好适用于5,但因为我在“其他”分支中跳过它而失败了6个。

答案 1 :(得分:1)

这是一种方法,但我确信有更优雅的方式。

方法是首先将我们的列表[a]映射到三元组列表[(Maybe a, a, Maybe a)]。 (Maybe发挥作用,因为第一个和最后一个元素分别缺少前任/后继元素。)

然后,我们可以根据我们在此三元类型上构造的谓词adjacentF来实现过滤器。 (请注意,您要求的过滤器向后与标准filter进行比较 - 您希望在谓词为真时删除事物。)

preprocess :: [a] -> [(Maybe a, a, Maybe a)]
preprocess xs = zip3 (beforeXs xs) xs (afterXs xs)

beforeXs :: [a] -> [Maybe a]
beforeXs xs = Nothing : (map Just xs)

afterXs :: [a] -> [Maybe a]
afterXs xs = concat [(map Just (tail xs)), [Nothing]]

middle3 :: (a, b, c) -> b
middle3 (_,x,_) = x

myfilter :: (a -> Bool) -> [a] -> [a]
myfilter f xs = map middle3 $ filter (not . adjacentF) (preprocess xs)
    where
        maybeF = maybe False f
        adjacentF (x,y,z) = (maybeF x) || (f y) || (maybeF z)

这通常会产生预期的结果:

*Main> myfilter (==20) [1..20]
[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18]
*Main> myfilter (==1) [1..20]
[3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
*Main> myfilter (==5) [1..20]
[1,2,3,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
*Main> myfilter (\x -> x >= 12 && x <= 14) [1..20]
[1,2,3,4,5,6,7,8,9,10,16,17,18,19,20]
*Main> myfilter even [1..20]
[]

答案 2 :(得分:1)

我的解决方案使用小型自制程序列表拉链实现:

-- List zipper (think of this as a standard library routine):
data LZ a = LZ [a] a [a] deriving (Show)

listToLZ :: [a] -> LZ a
listToLZ (h:t) = LZ [] h t

lzToList :: LZ a -> [a]
lzToList (LZ l p r) = reverse l ++ p:r

moveRight, remLeft, remRight, remHere :: LZ a -> LZ a
moveRight (LZ l t (t':r)) = LZ (t:l) t' r
remLeft (LZ l p r) = LZ (tail l) p r
remRight (LZ l p r) = LZ l p (tail r)
remHere (LZ l _ (p:r)) = LZ l p r

-- And there's how one use this:
-- <business code>
traverse :: (a -> Bool) -> LZ a -> LZ a
traverse _ a@(LZ _ _ []) = a
traverse pr a@(LZ _ p _) 
   | pr p = traverse pr $ remHere $ remRight $ remLeft a
   | True = traverse pr $ moveRight a
-- </business code>

main = let
  l = [1..20]
  l' = lzToList $ traverse (==4) $ listToLZ l
in
  print l'

输出:

[1,2,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]

答案 3 :(得分:0)

这是一个可以完成工作的解决方案,虽然可能不是最有效的方法:

f p l = map snd $ filter (flip notElem indices . fst) l'
  where
    indices = concat [[i - 1, i, i + 1] | (i, _) <- filter (p . snd) l']
    l' = zip [0..] l

使用findIndices中的Data.List来缩短一点:

f p l = map snd $ filter (flip notElem indices . fst) $ zip [0..] l
  where indices = concat [[i - 1, i, i + 1] | i <- findIndices p l]


测试:

*Main> f (\x -> x == 6 || x == 7) [2..20]
[2,3,4,9,10,11,12,13,14,15,16,17,18,19,20]

答案 4 :(得分:0)

您可以转置问题并查看保留元素的条件:如果位置n处的谓词为False,则保留位置n-1处的元素,nn+1

这指出了以下方法:

keep3 :: (a -> Bool) -> [a] -> [a]
keep3 f xs = go xs bs
  where b0 = map f xs
        b1 = (tail b0) ++ [False]
        b2 = False : b0
        bs = zipWith (||) (zipWith (||) b0 b1) b2
        go [] _ = []
        go (x:xs) (b:bs) = if (not b) then x : go xs bs else go xs bs

一些解释点:

  • b0是一个列表,其长度与元素列表的长度相同,其第n个值是在列表的第n个元素上计算的谓词的值。
  • b1b0相同,但向左移动了一个;所以它的第n个值是在列表的n+1 - 元素
  • 上评估的谓词
  • b2b0相同,但向右移动了一个;所以它的第n个值是在列表的n-1 - 元素
  • 上评估的谓词
  • bs是逻辑或列表b0b1b2按元素完成的,因此是与{{相同长度的布尔值列表1}}。 xs的第n个元素确定是否应保留bs的第n个元素。
  • 请注意,谓词仅针对列表xs的每个元素进行一次评估。另外我认为边界情况得到了正确处理。

答案 5 :(得分:0)

解决这个问题的一种方法是捕捉列表中项目邻域的概念。

-- A neighborhood of a point in a list 
data Neighborhood a = Neighborhood {
    predecessors :: ![a],
    point :: !a,
    successors :: ![a]
} deriving Show

我们可以通过在遍历列表时累积前导来轻松计算列表的邻域:

-- On long lists or infinite stream, this will leak memory (it keeps all the predecessors in memory)
neighborhoods :: [a] -> [Neighborhood a]
neighborhoods = neighborhoods' []
    where
        neighborhoods' _ [] = []
        neighborhoods' ps (x:ss) = (Neighborhood ps x ss):(neighborhoods' (x:ps) ss)

这些社区将包括每个列表项的所有前任和后继者。能够考虑较小的邻域within每个邻域的小半径都很方便。能够enumerate邻居也很方便。 (interleave的定义如下:它以交替顺序访问列表。)

within :: Int -> Neighborhood a -> Neighborhood a
within r n = Neighborhood (take r . predecessors $ n) (point n) (take r . successors $ n)

enumerate :: Neighborhood a -> [a]
enumerate n = (point n):(interleave (successors n) (predecessors n))

现在我们可以轻松地询问我们想要的东西 - 半径为1的邻域不包含不允许值的项目。 (powslog2定义如下/)

main = do
    let disallowed = \x -> x == pow2s !! log2 x
    print . map point . filter (not . any disallowed . enumerate . within 1) . neighborhoods $ [2..20]

如果我们要在非常大的列表上逐步工作,那么能够让垃圾收集器从邻近区域外收集前辈是有用的。这将是一个有效的版本:

neighborhoodsRadius n = map (within n) . neighborhoods

为了获得这种效率,我们需要严格地对附近的前辈进行评估。在下面的代码中,严格的评估是通过在遍历列表时使用Data.Sequence.Seq作为累加器来完成的。

这是完整的运行代码:

module Main (
    pow2s,
    log2,
    main,

    interleave,
    Neighborhood (..),
    within,
    enumerate,  
    neighborhoods,
    neighborhoodsRadius,
) where

import qualified Data.Sequence as Seq
import Data.Sequence ((<|))
import Data.Foldable (toList)

-- Interleave lists
interleave :: [a] -> [a] -> [a]
interleave [] ys = ys
interleave (x:xs) ys = x:(interleave ys xs)


-- A neighborhood of a point in a list 
data Neighborhood a = Neighborhood {
    predecessors :: ![a],
    point :: !a,
    successors :: ![a]
} deriving Show

within :: Int -> Neighborhood a -> Neighborhood a
within r n = Neighborhood (take r . predecessors $ n) (point n) (take r . successors $ n)

enumerate :: Neighborhood a -> [a]
enumerate n = (point n):(interleave (successors n) (predecessors n))

-- On long lists or infinite stream, this will leak memory (it keeps all the predecessors in memory)
neighborhoods :: [a] -> [Neighborhood a]
neighborhoods = neighborhoods' []
    where
        neighborhoods' _ [] = []
        neighborhoods' ps (x:ss) = (Neighborhood ps x ss):(neighborhoods' (x:ps) ss)

-- This is better on long lists or infinite stream as it will cull the predecessors before recursing
neighborhoodsRadius :: Int -> [a] -> [Neighborhood a]
neighborhoodsRadius radius = neighborhoods' Seq.empty
    where
        neighborhoods' _ [] = []
        neighborhoods' ps (x:ss) =
            (Neighborhood (toList radiusPs) x radiusSs):(neighborhoods' (x <| radiusPs) ss)
                where
                    radiusPs = Seq.take radius ps
                    radiusSs = take radius ss

-- example
pow2s = iterate (*2) 1
log2 n = length . takeWhile (< n) $ pow2s

main :: IO ()
main = do
    let disallowed = \x -> x == pow2s !! log2 x
    print . map point . filter (not . any disallowed . enumerate . within 1) . neighborhoods $ [2..20]
    getLine
    let steps = map (print . point) . filter (not . any disallowed . enumerate) . neighborhoodsRadius 1 $ [2..]
    sequence_ steps