Reputation: 301
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?
Upvotes: 3
Views: 1233
Reputation: 1510
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
Upvotes: 2
Reputation: 892
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
Upvotes: 0
Reputation: 16
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)
Upvotes: 0
Reputation: 61
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.
Upvotes: 0
Reputation: 233125
You could start by creating a distinct set of all the first elements, e.g.:
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.
Upvotes: 1