是否有一个简单的解决方案来接收元素* prior *并使其到达dropWhile谓词?

时间:2019-05-07 10:11:51

标签: haskell functional-programming

给出一个条件,我想搜索一个元素列表,然后返回达到条件的第一个元素和前一个。

在C / C ++中,这很容易:

int i = 0;
for(;;i++) if (arr[i] == 0) break;

获得满足条件的索引后,通过“ arr[i-1]”很容易获得上一个元素

在Haskell中:

  • dropWhile (/=0) list提供了我想要的最后一个元素

  • takeWhile (/=0) list给了我我想要的第一个元素

但是我看不到以简单的方式同时获得两者的方法。我可以枚举列表并使用索引,但这似乎很麻烦。有没有适当的方法可以做到这一点,或者有一种解决方法?

5 个答案:

答案 0 :(得分:8)

我会在列表尾部压缩列表,以便您拥有成对的元素 可用。然后,您可以在配对列表中使用find

f :: [Int] -> Maybe (Int, Int)
f xs = find ((>3) . snd) (zip xs (tail xs))

> f [1..10]
Just (3,4)

如果 first 元素与谓词匹配,它将返回 Nothing(如果有第二个匹配项,则为第二个匹配项),因此,如果您需要某些内容,可能需要特殊处理 不同。

罗宾·齐格蒙德(Robin Zigmond)所说的break也可以工作:

g :: [Int] -> (Int, Int)
g xs = case break (>3) xs of (_, []) -> error "not found"
                             ([], _) -> error "first element"
                             (ys, z:_) -> (last ys, z)

(或者也根据您的需要返回Maybe。)

但是,我认为这会将整个前缀ys保留在内存中,直到 找到匹配项,而f可以开始对元素进行垃圾收集 它已经过去了。对于小清单来说,没关系。

答案 1 :(得分:4)

我会使用类似拉链的搜索:

type ZipperList a = ([a], [a])

toZipperList :: [a] -> ZipperList a
toZipperList = (,) []

moveUntil' :: (a -> Bool) -> ZipperList a -> ZipperList a
moveUntil' _ (xs, []) = (xs, [])
moveUntil' f (xs, (y:ys))
    | f y       = (xs, (y:ys))
    | otherwise = moveUntil' f (y:xs, ys)

moveUntil :: (a -> Bool) -> [a] -> ZipperList a
moveUntil f = moveUntil' f . toZipperList

example :: [Int]
example = [2,3,5,7,11,13,17,19]

result :: ZipperList Int
result = moveUntil (>10) example -- ([7,5,3,2], [11,13,17,19])

关于拉链的好处是它们高效,您可以访问所需索引附近的任意多个元素,并且可以前后移动拉链的焦点。在此处了解有关拉链的更多信息:

http://learnyouahaskell.com/zippers

请注意,我的moveUntil函数类似于Prelude中的break,但列表的初始部分是相反的。因此,您只需获取两个列表的head

答案 2 :(得分:3)

将其实现为折叠的一种不尴尬的方式使其成为同形。有关一般的解释性说明,请参见this answer by dfeuer(我从中提取了foldrWithTails):

-- The extra [a] argument f takes with respect to foldr
-- is the tail of the list at each step of the fold.  
foldrWithTails :: (a -> [a] -> b -> b) -> b -> [a] -> b
foldrWithTails f n = go
    where
    go (a : as) = f a as (go as)
    go [] = n

boundary :: (a -> Bool) -> [a] -> Maybe (a, a)
boundary p = foldrWithTails findBoundary Nothing
    where
    findBoundary x (y : _) bnd
        | p y = Just (x, y)
        | otherwise = bnd
    findBoundary _ [] _ = Nothing

注意:

  • 如果p y为真,则不必查看bnd即可得到结果。这使得解决方案足够懒惰。您可以通过在GHCi中试用boundary (> 1000000) [0..]来进行检查。

  • 此解决方案不对与条件匹配的列表的第一个元素的边缘情况进行特殊处理。例如:

    GHCi> boundary (<1) [0..9]
    Nothing
    GHCi> boundary even [0..9]
    Just (1,2)
    

答案 3 :(得分:2)

有几种选择。无论哪种方式,您都必须自己实现。您可以使用显式递归:

getLastAndFirst :: (a -> Bool) -> [a] -> Maybe (a, a)
getLastAndFirst p (x : xs@(y:ys))
    | p y = Just (x, y)
    | otherwise = getLastAndFirst p xs
getLastAndFirst _ [] = Nothing

或者,您可以 使用折叠,但是除了可读性较差之外,它看起来与上面的十分相似。

第三个选择是使用break,如注释中所建议:

getLastAndFirst' :: (a -> Bool) -> [a] -> Maybe (a,a)
getLastAndFirst' p l =
    case break p l of
        (xs@(_:_), (y:_)) -> Just (last xs, y)
        _ -> Nothing

答案 4 :(得分:1)

(\(xs, ys) -> [last xs, head ys]) $ break (==0) list

如罗宾·齐格蒙德(Robin Zigmond)建议的那样,使用break简短而简单,没有使用Maybe来捕获边缘情况,但是我可以用一个简单的函数来替换lambda,该函数使用Maybe


我对解决方案玩得更多,并提出了

breakAround :: Int -> Int -> (a -> Bool) -> [a] -> [a]
breakAround m n cond list = (\(xs, ys) -> (reverse (reverse take m (reverse xs))) ++ take n ys) $ break (cond) list

,它接受两个整数,一个谓词和一个a的列表,并返回在谓词之前的m元素和后面的n元素的单个列表。

示例:breakAround 3 2 (==0) [3,2,1,0,10,20,30]将返回[3,2,1,0,10]