Reputation: 107
I have a structure defined as follows:
type P = [(Int, Int)]
I need to make a function from lists of that structure, add the items that are in first position in the tuple if the following condition is met: that the second element of the tuple is the same.
add :: [P] -> P
add lists = ......
E.g.
add [[(1,2), (3,0)], [(3,1), (7,0)]]
The result would be [(1,2), (3,1), (10.0)]
.
Since only would add tuples (3.0) with (7.0) because it matches the 0.
Upvotes: 3
Views: 1916
Reputation: 949
After merging and sorting (on second element), you can use a variant of group(By)
to combine the tuples that have the same second element.
import Data.List
import Data.Ord
add :: [P] -> P
add = combine . sortBy (comparing snd) . concat
where
combine [] = []
combine (x:xs) = (fst x + foldr ((+) . fst) 0 ys, snd x) : combine zs
where
(ys, zs) = span ((snd x ==) . snd) xs
Or you can use groupBy
to get one list for each distinct snd
value and fold each of those lists:
import Data.Function
add' :: [P] -> P
add' = map sumFst . groupBy ((==) `on` snd) . sortBy (comparing snd) . concat
where
sumFst = foldr1 $ \(a, b) (x, _) -> (a+x, b)
-- or, importing Data.Biapplicative from bifunctors package:
--sumFst = foldr1 $ (+) `biliftA2` const
Upvotes: 1
Reputation: 60463
This is a "keyed" operation, and can be solved quite easily with a little help from the standard library.
import qualified Data.Map as M
Data.Map
implements finite associative maps -- i.e. M.Map k a
has a set of keys of type k
, and to each key it associates a value of type a
.
The extremely useful function for this kind of problem is fromListWith
:
M.fromListWith :: (a -> a -> a) -> [(k,a)] -> M.Map k a
The second argument, the list of (k,a)
tuples, are simply associations, assocating a given key to a given value. The first argument is a combining function, which says what to do to the values if duplicate keys appear in the list.
You must also use M.toList :: M.Map k a -> [(k,a)]
which gives you back the list of associations stored in the Map. Here's an example:
ghci> M.toList (M.fromListWith (+) [(1,2), (2,3), (1,4), (3,5)])
[(1,6),(2,3),(3,5)]
Note how the keys are the first elements of the tuples, the opposite of how you have stated your problem. We have combined (1,2)
and (1,4)
into (1,6)
. We added because we gave the combining function (+)
.
This one function solves the brunt of your problem -- the rest is just a little plumbing, which I will leave to you.
Upvotes: 7
Reputation: 14578
From what I understand, you want to take a list of type [[(Int, Int)]]
and sum the first element of any tuple that has the same second element. You can completely disregard that it is a list of lists and just concat
right away, unless the structure of the lists can help you determine which elements should be added (but I have not seem any evidence that would indicate that). Then you get the minimum snd
, sort the list by the snd
values, and recurse through the list, calling span :: (a -> Bool) -> [a] -> ([a], [a])
which takes a predicate and splits a list at the last element which satisfies the predicate. Since the list is sorted, this will capture all of the tuples with same snd
. Then we fold over that list of tuples.
add :: [[(Int, Int)]] -> [(Int, Int)]
add list = addT (minBound :: Int) $ sortBy sortF (concat list)
where sortF (_,x) (_,y)
| x < y = LT
| x > y = GT
| x == y = EQ
addT n list = sumFunc $ span (sndIs n) list
where sndIs n (_,y) = y == n
sumFunc ([],b)= addT (n+1) b
sumFunc (a,[])= [(foldr (\(x,y) (w,v) -> (x+w,y)) (0,0) a)]
sumFunc (a,b) = (foldr (\(x,y) (w,v) -> (x+w,y)) (0,0) a):(addT (n+1) b)
then :
> add [[(1,2), (3,0)], [(3,1), (7,0)]]
> [(10,0),(3,1),(1,2)]
The only problem is the complexity is very bad (try add [[(43, minBound :: Int), (10, maxBound :: Int)]]
). If you want to make it faster, you should get all the snd
s first and only call addT with those, if you are likely to be using this function on values that differ greatly.
Upvotes: 1
Reputation: 71899
Let's decompose this problem.
So you have a list of lists
[
[(1, 0), (2, 1), (3, 2)]
[(2, 0), (3, 2), (4, 2)]
[(5, 0), (1, 1), (9, 9)]
]
and you want the sum of the vertical first elements if the second elements are all (all? or some?) the same, and the tuple in the first list otherwise (judging by your example, your spec doesn't actually say what happens).
The first thing you should do is transpose
this list, to get something more suited for our calculation.
[
[(1, 0), (2, 0), (5, 0)]
[(2, 1), (3, 2), (1, 1)]
[(3, 2), (4, 2), (9, 9)]
]
Now the problem is essentially a fold over each of these sublists. So you write a function that does it for one of these lists:
collapse :: [(Int, Int)] -> (Int, Int)
And then you map
this function over the entire transposed list:
add lists = map collapse $ transpose lists
// or point-free
add = map collapse . transpose
Now you only need to write collapse
.
Upvotes: 3