Reputation: 49
I am trying to write a function in Haskell that takes a list of integers and an integer n and finds all tuples that equal n. So far I have an implementation that works
tuplesum :: (Eq b, Num b) => [b] -> b -> [(b, b)]
tuplesum xs n = [(x1,x2) | x1 <- xs, x2 <- xs, x1 + x2 == n, x1 <= x2]
so if I gave this function the input
tuplesum [5,1,4,0,5,6,9] 10
the output is
[(5,5),(5,5),(1,9),(4,6),(5,5),(5,5)]
However, I have 4 duplicates of the (5,5) solution. I would like the function to output [(5,5),(1,9),(4,6)]
but I cant figure out how to constrain tuples that have the same integers without removing it as a solution entirely.
Upvotes: 2
Views: 380
Reputation: 714
I just really liked your function tuplesum xs n = [(x1,x2) | x1 <- xs, x2 <- xs, x1 + x2 == n, x1 <= x2]
because it is a Cartesian product and it eliminates most symmetrical pairs that would otherwise constitute about half. It gets the predicate matches so very well. The only problem remaining is the duplicate elements. I had forgotten about this until recently and Pg. 86 of Graham Hutton's "Programming in Haskell" and his rmdups
function. What I like about his rmdups
is that it neither depends on imports.
rmdups :: Eq a => [a] -> [a]
rmdups [] = []
rmdups (x:xs) = x : filter (/= x) (rmdups xs)
Hutton's solution is wonderfully general and classically recursive. I did not want to post his solution here without adding something original so here is a list comprehension to eliminate duplicates of any data type including tuples.
rmdups ls = [d|(z,d)<- zip [0..] ls, notElem d $ take z ls]
You can place either rmdups
function in front of your function rmdups.tuplesum
Your function eliminates most symmetrical pairs so rmdups
does not.
rmdups [(5,5),(5,5),(1,9),(4,6),(5,5),(5,5)]
[(5,5),(1,9),(4,6)]
Or
rmdups "abcabcdefdef" OR "abcdefabcdef"
"abcdef"
Upvotes: 0
Reputation: 476584
I have the impression that you are looking for a way to select two elements out of the list such that x1
is always located before x2
.
A common way to always let x2
iterate over the remainder of the list is by using tails :: [a] -> [[a]]
. For a list, tails
will generate a list of all tails of the list, starting with the list itself. For example:
Prelude Data.List> tails [1, 4, 2, 5]
[[1,4,2,5],[4,2,5],[2,5],[5],[]]
We can use this with pattern matching to select one element, and get a reference to the remaining element. For example:
import Data.List(tails)
tuplesum :: (Eq b, Num b) => [b] -> b -> [(b, b)]
tuplesum xs n = [(x1,x2) | (x1:x2s) <- tails xs, x2 <- x2s, x1 + x2 == n]
Note that it is still possible to obtain duplicates here, for example if 5
would occur three times in the list, since in that case x1
can select the first 5
, and then x2
can select the second 5
as well as the last one. We can make use of a uniqness filter like nub :: Eq a => [a] -> [a]
for this:
import Data.List(nub, tails)
tuplesum :: (Eq b, Num b) => [b] -> b -> [(b, b)]
tuplesum xs n = nub [(x1,x2) | (x1:x2s) <- tails xs, x2 <- x2s, x1 + x2 == n]
Note that it is however still better to use tails
here, since it will increase performance, since we will simply generate a smaller amount of duplicates in the first place.
The above algorithm is still O(n2), and not very fast. We can however solve the problem the other way around: we can first construct a HashSet
of the elements, and the for each element x1
, check if n - x1
is a member, like:
import Data.Hashable(Hashable)
import Data.HashSet(fromList, member)
tuplesum :: (Ord b, Hashable b, Num b) => [b] -> b -> [(b, b)]
tuplesum xs n = nub [(x1,x2) | x1 <- xs, let x2 = n-x1, x1 <= x2, member x2 hs]
where hs = fromList xs
But the runtime is still O(n2) because of the nub
, we can however use a hashNub :: (Eq a, Hashable a) => [a] -> [a]
here:
hashNub :: (Eq a, Hashable a) => [a] -> [a] hashNub = go HashSet.empty where go _ [] = [] go s (x:xs) = if x `HashSet.member` s then go s xs else x : go (HashSet.insert x s) xs
and then let it work with:
import Data.Hashable(Hashable)
import Data.HashSet(fromList, member)
tuplesum :: (Ord b, Hashable b, Num b) => [b] -> b -> [(b, b)]
tuplesum xs n = hashNub [(x1,x2) | x1 <- xs, let x2 = n-x1, x1 <= x2, member x2 hs]
where hs = fromList xs
Now it works in O(n log n).
Upvotes: 6