我正在编写一个带有列表和两个可能包含在列表中的元素的函数。该函数应该返回一个结构中的两个元素,这些元素按它们在列表中的出现进行排序。
所以,对于数字,我们有这样的事情:
xs = [4,6,3,2,1,8]
f (3,1) --> (Just 3, Just 1)
f (1,3) --> (Just 3, Just 1)
f (9,1) --> (Just 1, Nothing)
f (9,9) --> (Nothing, Nothing)
依旧......
我在那里使用了元组,因为我实际上只对这两个值而不是任意数字感兴趣。但如果有原因,将其建模为列表也是可以的。
无论如何,这是我提出的功能:
f :: Eq a => [a] -> (a, a) -> (Maybe a, Maybe a)
f xs (a, b) = foldl g (Nothing, Nothing) xs where
g (Nothing, Nothing) x | x == a = (Just a, Nothing)
g (Nothing, Nothing) x | x == b = (Just b, Nothing)
g (Just a', Nothing) x | a' == a && x == b = (Just a, Just b)
g (Just b', Nothing) x | b' == b && x == a = (Just b, Just a)
g m x = m
它的工作,但我认为它有很多模式匹配,它有点容易出错。那么,有没有人对这个问题有更好的抽象?
答案 0 :(得分:1)
如果你想减少模式匹配的数量,那么最好不要递归地传递对(Maybe a, Maybe a)
并在其上进行模式匹配。您可以将函数拆分为两个递归函数,其中第一个函数找到第一个元素,第二个函数调用另一个函数。这可以这样做:
f :: Eq a => (a, a) -> [a] -> (Maybe a, Maybe a)
f (a, b) = goFirst
where
goFirst [] = (Nothing, Nothing)
goFirst (x:xs)
| x == a = (Just a, goSecond b xs)
| x == b = (Just b, goSecond a xs)
| otherwise = goFirst xs
goSecond _ [] = Nothing
goSecond y (x:xs)
| x == y = Just y
| otherwise = goSecond y xs
这不是你想要的那么简短和优雅,但它可读,快速(我想补充一点,你永远不应该使用foldl
函数)并且不易出错。
如果你正在寻找一些抽象,你可以看看First
monoid with pair monoid。对First
数据类型使用monoid实例,您可以从以下内容开始:
import Data.Bifunctor (bimap)
import Data.Monoid (First (..), mconcat)
g :: Eq a => (a, a) -> [a] -> (Maybe a, Maybe a)
g (a, b) = bimap getFirst getFirst . mconcat . map fMapper
where
fMapper x
| x == a = (First (Just a), mempty)
| x == b = (mempty, First (Just b))
| otherwise = mempty
虽然这个功能并不能完全符合您的要求:
ghci> let xs = [4,6,3,2,1,8]
ghci> g (3, 1) xs
(Just 3,Just 1)
ghci> g (1, 3) xs
(Just 1,Just 3)
要使用此方法实现初始目标,您可以为每个元素添加索引,然后通过索引对First
下的对进行排序,但此解决方案非常可怕。使用First
monoid很诱人,但我不知道如何在这里优雅地使用它。
但是你可以结合第一和第二种解决方案的想法:
import Data.Bool (bool)
import Data.Monoid (First (..))
h :: Eq a => (a, a) -> [a] -> (Maybe a, Maybe a)
h (a, b) = goFirst
where
goFirst [] = (Nothing, Nothing)
goFirst (x:xs)
| x == a = (Just a, goSecond b xs)
| x == b = (Just b, goSecond a xs)
| otherwise = goFirst xs
goSecond y = getFirst . foldMap (bool mempty (First (Just y)) . (== y))
答案 1 :(得分:1)
以下是一种可能的解决方案,其中包含以下类型的列表:
f :: Eq a => [a] -> [a] -> [Maybe a]
我会调用列表来搜索haystack
以及要搜索needles
的元素。首先,我们可以针对每个haystack
搜索needle
,并使用findIndex
返回一对值及其找到的索引(如果有):
findIndices needles haystack =
[ (needle, findIndex (== needle) haystack)
| needle <- needles
]
findIndices [1, 3] xs == [(1, Just 4), (3, Just 2)]
(请注意,这总是使用第一个出现的索引 - 我不确定这是否是您想要的。您可以将其扩展为折叠,以便在找到时删除每个匹配项。)
然后按索引对此列表进行排序:
sortBy (comparing snd) [(1, Just 4), (3, Just 2)]
==
[(3, Just 2), (1, Just 4)]
最后使用(<$) :: Functor f => a -> f b -> f a
:
[value <$ mIndex | (value, mIndex) <- [(3, Just 2), (1, Just 4)]]
==
[Just 3, Just 1]
(x <$ f
相当于const x <$> f
。)
但是当我们在没有找到某些元素的输入上尝试这个时,我们会得到错误的结果,其中Nothing
来自开头而不是结尾:
findIndices [9, 1] xs == [(9, Nothing), (1, Just 4)]
sortBy (comparing snd) [(9, Nothing), (1, Just 4)]
==
[(9, Nothing), (1, Just 4)]
这是因为Nothing
被认为低于任何Just
值。由于我们希望相反,我们可以使用Maybe
中的Down
新类型来反转Data.Ord
的排序顺序,方法是将Down . snd
代替snd
作为比较器:
sortBy (comparing (Down . snd)) [(9, Nothing), (1, Just 4)]
==
[(1, Just 4), (9, Nothing)]
但是这个也会反转索引本身的排序顺序,这是我们不想要的:
sortBy (comparing (Down . snd)) [(1, Just 4), (3, Just 2)]
==
[(1, Just 4), (3, Just 2)]
因此我们可以在索引周围添加另一个Down
:
findIndices needles haystack =
[ (needle, Down <$> findIndex (== needle) haystack)
| needle <- needles
]
sortBy (comparing Down) [Just (Down 2), Nothing, Just (Down 1)]
==
[Just (Down 1), Just (Down 2), Nothing]
sortBy (comparing (Down . snd))
[(1, Down (Just 4)), (3, Down (Just 2))]
==
[(3, Down (Just 2)), (1, Down (Just 4))]
最后把它们放在一起:
f :: (Eq a) => [a] -> [a] -> [Maybe a]
f needles haystack =
[ value <$ index
| (value, index) <- sortBy (comparing (Down . snd))
[ (needle, Down <$> findIndex (== needle) haystack)
| needle <- needles
]
]
f [1, 3] xs == [Just 3, Just 1]
f [3, 1] xs == [Just 3, Just 1]
f [1, 9] xs == [Just 1, Nothing]
f [9, 9] xs == [Nothing, Nothing]
或者,没有列表推导和更短的名称:
f :: (Eq a) => [a] -> [a] -> [Maybe a]
f ns hs
= map (\ (v, i) -> v <$ i)
$ sortBy (comparing (Down . snd))
$ map (\ n -> (n, Down <$> findIndex (== n) hs)) ns
\ (v, i) -> v <$ i
也可以写成uncurry (<$)
,但如果您不习惯无点风格,那可能会有点神秘。此外,如果您不关心Nothing
,则可以使用mapMaybe
代替map
,将返回类型从[Maybe a]
更改为[a]
}。
答案 2 :(得分:0)
我不知道你会认为它们有多好,但你可以做一些事情来更多地使用列表功能。
我最初想过滤掉无关紧要的东西 首先是项目,然后是分组:
f :: Eq a => [a] -> (a,a) -> (Maybe a, Maybe a)
f xs (a, b) =
case (map head . group . filter (`elem` [a,b])) xs of
[] -> (Nothing, Nothing)
[c] -> (Just c, Nothing)
(c:d:_) -> (Just c, Just d)
但是这与你的实现不一样,例如,
f [8,9,9] (9,9)
,所以你需要特殊情况,如果是这样的话
你关心的。
另一种方法是使用dropWhile
:
f' :: Eq a => [a] -> (a,a) -> (Maybe a, Maybe a)
f' xs (a, b) =
case dropWhile (`notElem` [a, b]) xs of
[] -> (Nothing, Nothing)
(y:ys) -> (Just y, next)
where
next = case dropWhile (/=other) ys of
[] -> Nothing
(z:_) -> Just z
other = if y == a then b else a
内部案例实际上只是find
所以它可以简化为a
更多:
f'' :: Eq a => [a] -> (a,a) -> (Maybe a, Maybe a)
f'' xs (a, b) =
case dropWhile (`notElem` [a, b]) xs of
[] -> (Nothing, Nothing)
(y:ys) -> (Just y, find (==other) ys)
where
other = if y == a then b else a
注意:这些函数永远不会返回(Nothing, Just _)
形式的结果。这表明返回类型为Maybe (a, Maybe a)
可能会更好。或者是None | One a | Two a a
等自定义类型。
或者,我们可以推广到允许as的列表版本 你喜欢的许多目标值。这是一个很好的展开:
f''' :: Eq a => [a] -> [a] -> [a]
f''' xs ts = unfoldr g (xs, ts)
where
g (ys, us) = case dropWhile (`notElem` us) ys of
[] -> Nothing
(z:zs) -> Just (z, (zs, delete z us))
其中的工作原理如下:
λ> f''' [4,2,5,3,1] [1,2,3]
[2,3,1]
λ> f''' [4,2,5,3,1] [1,2,6]
[2,1]
λ> f''' [7,9,8,9] [9,9]
[9,9]
我几乎在这里重新发明intersect
但不完全。它具有我们想要保留第一个列表中的顺序的行为,但它在重复上是不一样的 - 例如intersect [4,2,2,5] [1,2]
为[2,2]
。