简体   繁体   中英

Haskell - Filtering a list of tuples

Consider this list of tuples:

[(57,48),(58,49),(59,50),(65,56),(65,47),(65,57),(65,49), (41, 11)]

I want to remove a tuple (a, b) if its second element b is equal to the first element of another tuple and all the tuples with the same a that come after it. For example:

The second element of (65,57) is 57 and the first tuple in the list (57,48) has 57 as its first element, so (65,57) should be removed and all tuples that come after it that start with 65, namely (65,49) . The tuples that come before it, (65,56) and (65,47) , should stay in the list.

Does anyone have an idea how to do this?

For efficiency (single pass), you should create two sets, one for elements you've seen as the first elements of tuples, the other for elements you've seen both as first and second elements (ie. delete if matches first element).

Something like,

{-# LANGUAGE PackageImports #-}

import "lens" Control.Lens (contains, (.~), (^.), (&))
import "yjtools" Data.Function.Tools (applyUnless, applyWhen)
import qualified "containers" Data.IntSet as Set

filterTuples :: Foldable t => t (Int, Int) -> [(Int, Int)]
filterTuples = flip (foldr go $ const []) (Set.empty, Set.empty)
  where
    go p@(x,y) go' (fsts, deletes) =
      let seenFst = fsts ^. contains y
          shouldDelete = seenFst || deletes ^. contains x
          fsts' = fsts & contains x .~ True
          deletes' = deletes & applyWhen seenFst (contains y .~ True)
      in  applyUnless shouldDelete (p:) $ go' (fsts', deletes')

EDITs: for correctness, clarity, spine-laziness

You could start by creating a distinct set of all the first elements, eg:

Prelude Data.List> firsts = nub $ fst <$>
                              [(57,48),(58,49),(59,50),(65,56),(65,47),
                               (65,57),(65,49), (41, 11)]
Prelude Data.List> firsts
[57,58,59,65,41]

You could use break or span as Robin Zigmond suggests. You'll need a predicate for that. You could use elem , like this:

Prelude Data.List> elem 48 firsts
False
Prelude Data.List> elem 49 firsts
False
...
Prelude Data.List> elem 57 firsts
True

If you're concerned that elem is too inefficient, you could experiment with creating a Set and use the member function instead.

I'm an absolute beginner in haskell, so there probably is a much more elegant/efficient solution for this. But anyways I wanted to share the solution I came up with:

filterTuples :: [(Int, Int)] -> [(Int,Int)]
filterTuples [] = []
filterTuples (x:xs) = x:filterTuples(concat ((fst temp) : [filter (\z -> fst z /= del) (snd temp)]))
    where del = fst (head (snd temp))
          temp = break (\y -> (snd y == fst x)) xs 

(Glad for feedback on how to improve this)

Perhaps try using mapAccumL starting with the initial list as the accumulator. Then maintain a Predicate as a parameter too which acts as a decider for what has been seen, and this will determine if you can output or not at each step in the traversal.

f consumes a list of pairs: xs ; it produces a new list of pairs: ys . ys contains every pair: (a, b) in xs , except the pair whose second element b : previously occurred as first elements: a . When such a pair: (a, b) is encountered, subsequent pairs that have a as their first elements are excluded from ys .

f xs = go xs [] []
  where
    go [] ys zs        = ys
    go (x@(a,b):xs) ys zs 
      |  b `elem` as  = go xs ys (a:zs)
      |  a `elem` zs  = go xs ys zs
      | otherwise     = [x] ++ go xs ys zs
    as = (nub . fst . unzip) xs

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM