I am trying to write a function that takes three lists as arguments and creates one list with a triple from each list consecutively.
The example I was given is this: zip3Lists [1, 2, 3] [4, 5, 6] ['a', 'b', 'c']
would produce [(1, 4, 'a'), (2, 5, 'b'), (3, 6, 'c')]
.
What I have so far is this:
zipThree [] [] [] = []
zipThree [] [] [x] = [x]
zipThree [] [x] [] = [x]
zipThree [x] [] [] = [x]
zipThree (x:xs) (y:ys) (z:zs) = (x, y, z) : zipThree xs ys zs
and it is giving me this error:
haskell1.hs:32:33: error:
• Occurs check: cannot construct the infinite type: c ~ (c, c, c)
Expected type: [c]
Actual type: [(c, c, c)]
• In the expression: (x, y, z) : zipThree xs ys zs
In an equation for ‘zipThree’:
zipThree (x : xs) (y : ys) (z : zs) = (x, y, z) : zipThree xs ys zs
• Relevant bindings include
zs :: [c] (bound at haskell1.hs:32:27)
z :: c (bound at haskell1.hs:32:25)
ys :: [c] (bound at haskell1.hs:32:20)
y :: c (bound at haskell1.hs:32:18)
xs :: [c] (bound at haskell1.hs:32:13)
x :: c (bound at haskell1.hs:32:11)
(Some bindings suppressed; use -fmax-relevant-binds=N or -fno-max-relevant-binds)
First of all let's add a type signature. From the question it seems as if the following type signature is appropriate: zipThree :: [a] -> [b] -> [c] -> [(a, b, c)]
This takes 3 lists (containing possibly different types of objects) and then produces a list of triples.
You handle the empty list case fine: zipThree [] [] [] = []
Then the problem occurs. As stated in the comments you have cases for the lists having different lengths but that give a different type of output.
I'll annotate the types next to each line so you can see:
zipThree [] [] [x] = [x] :: [c]
zipThree [] [x] [] = [x] :: [b]
zipThree [x] [] [] = [x] :: [a]
These don't fit with the other two cases that have type [(a, b, c)]
.
You mentioned in the comments that you will just presume the lengths are the same size therefore just removing these cases is sufficient. This gives:
zipThree [] [] [] = []
zipThree (x:xs) (y:ys) (z:zs) = (x, y, z) : zipThree xs ys zs
Which provides the correct output ( [(1, 4, 'a'), (2, 5, 'b'), (3, 6, 'c')]
) for the input you gave ( [1, 2, 3] [4, 5, 6] ['a', 'b', 'c']
).
This function of course will fail on inputs where the lists are of different lengths. One way to stop a straight up error and allow you to handle the issue would be to wrap the result in a Maybe.
First we need to change the type to: zipThree :: [a] -> [b] -> [c] -> Maybe [(a, b, c)]
The Maybe data type can either be a value wrapped in Just so Just a
or Nothing
.
For the empty list we want to give just the empty list: zipThree [] [] [] = Just []
.
Naturally you might think that the next case should be: zipThree (x:xs) (y:ys) (z:zs) = Just $ (x, y, z) : zipThree xs ys zs
.
But this doesn't work. Don't forget zipThree xs ys zs
now has type Maybe [(a, b, c)]
whereas (x, y, z)
has type (a, b, c)
so we can't add it to the list.
What we need to do is check the result of zipThree xs ys zs
if it failed at some point during the recursion then it will be Nothing
so we just want to pass that Nothing
along again. If it succeeded and gave us Just as
then we want to add our (x, y, z)
to that list. We can check which case is relevant using case of
:
zipThree (x:xs) (y:ys) (z:zs) = case zipThree xs ys zs of
Nothing -> Nothing
Just as -> Just $ (x, y, z) : as
We will know our lists aren't the same length if at some point during the recursion some lists are empty and others aren't. This doesn't match either pattern we have at the moment [] [] []
or (x:xs) (y:ys) (z:zs)
so we need one final catch all case to give us that Nothing
and prevent the error:
zipThree _ _ _ = Nothing
This gives a final definition of:
zipThree :: [a] -> [b] -> [c] -> Maybe [(a, b, c)]
zipThree [] [] [] = Just []
zipThree (x:xs) (y:ys) (z:zs) = case zipThree xs ys zs of
Nothing -> Nothing
Just as -> Just $ (x, y, z) : as
zipThree _ _ _ = Nothing
The results for the examples are:
zipThree [1, 2, 3] [4, 5, 6] ['a', 'b', 'c', 'd'] = Nothing
and
zipThree [1, 2, 3] [4, 5, 6] ['a', 'b', 'c'] = Just [(1, 4, 'a'), (2, 5, 'b'), (3, 6, 'c')]
.
Hope this helps, feel free to ask for clarification :)
EDIT: As suggested in the comments the following definitions would stop short in the case the lists are different lengths:
zipThree :: [a] -> [b] -> [c] -> [(a, b, c)]
zipThree (x:xs) (y:ys) (z:zs) = (x, y, z) : zipThree xs ys zs
zipThree _ _ _ = []
zipThree :: [a] -> [b] -> [c] -> Maybe [(a, b, c)]
zipThree (x:xs) (y:ys) (z:zs) = case zipThree xs ys zs of
Nothing -> Just [(x, y, z)] -- Change is here
Just as -> Just $ (x, y, z) : as
zipThree _ _ _ = Nothing
PS Thanks to the guy who added the missing Just in an edit.
There is this ZipList
type defined in Control.Applicative
module which is in fact exactly thought for this job.
ZipList
type is derived from the List
type like
newtype ZipList a = ZipList { getZipList :: [a] }
deriving ( Show, Eq, Ord, Read, Functor, Foldable
, Generic, Generic1)
Unlike normal List
s it's Applicative
instance does not work on combinations but one to one on corresponding elements like zipping. Hence the name ZipList
. This is the Applicative
instance of ZipList
instance Applicative ZipList where
pure x = ZipList (repeat x)
liftA2 f (ZipList xs) (ZipList ys) = ZipList (zipWith f xs ys)
The advantage of zipList
is we chain up indefinitely many lists to zip with. So when zipWith7
is not sufficient you may still carry on with a ZipList
. So here is the code;
import Control.Applicative
zip'mAll :: [Int] -> [Int] -> String -> [(Int,Int,Char)]
zip'mAll xs ys cs = getZipList $ (,,) <$> ZipList xs <*> ZipList ys <*> ZipList cs
*Main> zip'mAll [1,2,3] [4,5,6] "abc"
[(1,4,'a'),(2,5,'b'),(3,6,'c')]
Firstly, we need a type signature, as stated by James Burton, who lists a suitable one also:
zipThree :: [a] -> [b] -> [c] -> [(a, b, c)]
Essentially, this type signature says that, given three lists of any type a, b or c, a list of three-value tuples whose type is (a, b, c) shall be produced.
If we disregard the need to handle invalid cases (empty lists, variable-length lists), we next need to implement a valid case which produces the correct tuple from the lists given. Your statement
zipThree (x:xs) (y:ys) (z:zs) = (x, y, z) : zipThree xs ys zs
is valid. Therefore, thus far we have:
zipThree :: [a] -> [b] -> [c] -> [(a, b, c)]
zipThree (x:xs) (y:ys) (z:zs) = (x, y, z) : zipThree xs ys zs
The problem occurs when you introduce the cases for your invalid lists:
zipThree [] [] [x] = [x]
zipThree [] [x] [] = [x]
zipThree [x] [] [] = [x]
When one of these cases is matched, the type attempting to be bound is invalid due to being of type [x], where type (x, y, z) is expected.
You could exhaustively attempt to match base cases before recursively accessing the function again. However, you could also simply declare the case
zipThree _ _ _ = []
after, which will end the recursion with invalid input.
Putting this altogether, we are left with:
zipThree :: [a] -> [b] -> [c] -> [(a, b, c)]
zipThree (x:xs) (y:ys) (z:zs) = (x, y, z) : zipThree xs ys zs
zipThree _ _ _ = []
What's good about this implementation is that the recursion ends when any list is empty, thus stopping short for uneven lists, eg
zipThree [1, 2, 3] [4, 5, 6] [7, 8]
would produce
[(1, 4, 7), (2, 5, 8)]
Good luck!
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.