给出一个条件,我想搜索一个元素列表,然后返回达到条件的第一个元素和前一个。
在C / C ++中,这很容易:
int i = 0;
for(;;i++) if (arr[i] == 0) break;
获得满足条件的索引后,通过“ arr[i-1]
”很容易获得上一个元素
在Haskell中:
dropWhile (/=0) list
提供了我想要的最后一个元素
takeWhile (/=0) list
给了我我想要的第一个元素
但是我看不到以简单的方式同时获得两者的方法。我可以枚举列表并使用索引,但这似乎很麻烦。有没有适当的方法可以做到这一点,或者有一种解决方法?
答案 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]