简体   繁体   中英

haskell how to check two lists of tuples are equal and take union

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:

  • To check equality, just check that looking up each key in the first list gives the same result as looking it up in the second list.
  • To compute the sums, just pair every key up with the sum of lookups in both original lists.

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.

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