在列表中找到两个元素,按其出现顺序排列

时间:2017-05-02 06:36:11

标签: haskell

我正在编写一个带有列表和两个可能包含在列表中的元素的函数。该函数应该返回一个结构中的两个元素,这些元素按它们在列表中的出现进行排序。

所以,对于数字,我们有这样的事情:

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

它的工作,但我认为它有很多模式匹配,它有点容易出错。那么,有没有人对这个问题有更好的抽象?

3 个答案:

答案 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]