I am a new self-leaner in Haskell. firstly, I want to write a function to check if two lists of tuples are equal. Each tuple has a key and value
secondly, I want a function to union two lists of tuples
I tried several ways and tried many times but seems couldn't meet my requirements. could anyone help me? thanks in advance.
Since a
is only a member of Eq
, sorting or grouping is not an option.
import Data.List(nub, (\\))
import Data.Monoid(getSum)
type Times = Int
type Lis a = [(a,Times)]
lisEqual :: Eq a => Lis a -> Lis a -> Bool
lisEqual xs xs' = length xs == length xs' && xs \\ xs' == []
lisSum :: Eq a => Lis a-> Lis a-> Lis a
lisSum xs xs' = fmap f $ getKeys l
where
f x = (,) x (getSum . foldMap (pure . snd) . filter ((x ==) . fst) $ l)
l = xs ++ xs'
getKeys = nub . fst . unzip
My suggestion: Start with a function that extracts the combined keys from two lists:
allKeys :: Eq a => Lis a -> Lis a -> [a]
So allKeys [('a',2),('b',2),('c',3)] [('b',2),('a',1),('d',3)]
is ['a','b','c','d']
. Hint: Extract all keys from both lists, merge them to one list, then remove duplicates from that list (there are standard functions for all these tasks).
The function is useful both for checking equality and computing the sums:
One thing to consider: is the list [('a',0)]
is ment to be identical to []
? Otherwise you should use a lookup function that returns Maybe Int
and gives Just 0
for key 'a' in the first case and Nothing
in the second case.
Let me know if this is not homework and I can give you code.
Edit: Code! :)
The code below is slightly simplified compared to how I would normally write it, but not by much. There are probably several library functions you are not familiar with, including nub (for removing duplicates) which is imported from Data.List.
import Data.List(nub)
type Times = Int
type Lis a = [(a,Times)]
count :: Eq a => Lis a -> a -> Times
count xs x = case lookup x xs of
Nothing -> 0 -- x is not in the list
Just n -> n -- x is in the list associated with n
-- Extract all keys by taking the first value in each pair
keys :: Lis a -> [a]
keys xs = map fst xs
-- Extract the union of all keys of two lists
allKeys :: Eq a => Lis a -> Lis a -> [a]
allKeys xs ys = nub (keys xs ++ keys ys)
lisEquals :: Eq a=> Lis a -> Lis a -> Bool
lisEquals xs ys = all test (allKeys xs ys)
where
-- Check that a key maps to the same value in both lists
test k = count xs k == count ys k
lisSum :: Eq a => Lis a -> Lis a -> Lis a
lisSum xs ys = map countBoth (allKeys xs ys)
where
-- Build a new list element from a key
countBoth k = (k,count xs k + count ys k)
This is the version I proposed in the comments. First check the lists for duplicate keys and equal length to ensure that we only need to check if all keys of l1
are keys of l2
. Then do the lookup and check if the counts are equal:
lisEqual l1 l2 =
(nodups $ map fst l1) &&
(nodups $ map fst l2) &&
length l1 == length l2 &&
and (map (\ (x,k) -> case (occOfA x l2) of
Just n -> n == k
Nothing -> False
) l1)
The lookup returns Maybe b
to indicate a failed lookup with Nothing
.
occOfA :: Eq a => a -> [(a,b)] -> Maybe b
occOfA a [] = Nothing
occOfA a ((x,n):xs) =
if a == x then Just n
else occOfA a xs
The duplicate checking is just a recursion
nodups :: Eq a => [a] -> Bool
nodups [] = True
nodups (x:xs) = not (x `elem` xs) && (nodups xs)
Some test cases
t :: Int -> Bool
t 0 = lisEqual [(2,3), (1,2)] [(1,2), (2,3)] == True
t 1 = lisEqual [(2,3), (1,2)] [(1,3), (2,3)] == False
t 2 = lisEqual [(2,3), (1,2), (1,3)] [(1,3), (2,3)] == False
t 3 = lisEqual [(2,3)] [(1,3), (2,3)] == False
can be checked as
*Main> and $ map t [0..3]
True
I'm a bit lazy for computing the sums, I define a function lisSum1
that collects all keys from list and sums up the values accordingly. For lisSum
I just need to concatenate the two lists:
lisSum l1 l2 = lisSum1 $ l1 ++ l2
lisSum1 :: Eq a => [(a,Int)] -> [(a,Int)]
lisSum1 list =
reverse $ foldl (\acc k -> (k, sumList $ map snd (select k list) ) : acc ) -- create pairs (k, ksum) where ksum is the sum of all values with key k
[] (rdups $ map fst list)
With some helper functions:
rdups :: Eq a => [a] -> [a]
rdups [] = []
rdups (x:xs) = x : rdups (filter (/= x) xs)
sum l = foldl (+) 0 l
select k list = filter (\ (x,_) -> k == x) list
Some tests again:
s :: Int -> Bool
s 0 = lisSum [('a',1), ('a',2)] [('a',3)] == [('a',6)]
s 1 = lisSum [(1,2), (2,3)] [(2,4),(3,1)] == [(1,2),(2,7),(3,1)]
s 2 = lisSum [(1,2), (2,3), (2,4), (3,1)] [] == [(1,2),(2,7),(3,1)]
s 3 = lisSum [(1,2), (2,3), (3,1)] [] == [(1,2),(2,3),(3,1)]
*Main> map s [0..3]
[True,True,True,True]
Edit : The function lisEqual
is not reflexive because we originally defined a version that requires no duplicates in the input. The problem with this is that lisEqual
is no equivalence relation:
*Main> lisEqual [(1,1),(1,2)] [(1,1),(1,2)]
False
If we fix the reflexivity, we can just remove the original restriction on duplicates and define:
lisEqualD [] [] = True
lisEqualD (_:_) [] = False
lisEqualD [] (_:_) = False
lisEqualD (x:xs) ys =
case (remFirst x ys) of
Nothing -> False
Just zs -> lisEqualD xs zs
remFirst x [] = Nothing
remFirst x (y:ys) =
if x == y then Just ys
else case (remFirst x ys) of
Just zs -> Just (y:zs)
Nothing -> Nothing
Let's extend the test cases:
t :: Int -> Bool
t 0 = lisEqualD [(2,3), (1,2)] [(1,2), (2,3)] == True
t 1 = lisEqualD [(2,3), (1,2)] [(1,3), (2,3)] == False
t 2 = lisEqualD [(2,3), (1,2), (1,3)] [(1,3), (2,3)] == False
t 3 = lisEqualD [(2,3)] [(1,3), (2,3)] == False
t 4 = lisEqualD [(2,3), (1,2), (2,3)] [(1,2), (2,3),(2,3)] == True
t 5 = lisEqualD [(1,1),(1,2)] [(1,1),(1,2)] == True
*Main> map t [0..5]
[True,True,True,True,True,True]
My solution is pretty straightforward. In order to compare such lists you need to order them first. Sum two list by key can be done recursively as long as the key is of type Ord
and you order by key both lists. I am not using your aliases just to keep it primitive, but you can easily adapt it
eqList xs vs = xs' == vs'
where xs' = sortOn fst xs
vs' = sortOn fst vs
sumKeyValue' :: [(Char, Integer)] -> [(Char, Integer)] -> [(Char, Integer)]
sumKeyValue' [] v = v
sumKeyValue' x [] = x
sumKeyValue' x@((a, c):xs) v@((b,d):vs)
| a == b = (a, c + d):sumKeyValue xs vs
| a < b = (a,c):sumKeyValue xs v
| a > b = (b,d):sumKeyValue x vs
sumKeyValue xs vs = sumKeyValue' xs' vs'
where xs' = sortOn fst xs
vs' = sortOn fst vs
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.