14

How can I zip two lists like

["Line1","Line2","Line3"]
["Line4","Line5"]

without discarding rest elements in first list?

I'd like to zip extra elements with empty list, if it can be done.

zerospiel
  • 632
  • 8
  • 20

5 Answers5

15
zipWithPadding :: a -> b -> [a] -> [b] -> [(a,b)]
zipWithPadding a b (x:xs) (y:ys) = (x,y) : zipWithPadding a b xs ys
zipWithPadding a _ []     ys     = zip (repeat a) ys
zipWithPadding _ b xs     []     = zip xs (repeat b)

As long as there are elements, we can simply zip them. As soon as we run out of elements, we simply zip the remaining list with an infinite list of the padding element.

In your case, you would use this as

zipWithPadding "" "" ["Line1","Line2","Line3"] ["Line4","Line5"]
-- result: [("Line1","Line4"),("Line2","Line5"),("Line3","")]
Zeta
  • 103,620
  • 13
  • 194
  • 236
  • 3
    Note to those who want to see `mempty` or `Monoid`: `zipWithPadding mempty mempty` will give you your `(Monoid a, Monoid b) => [a] -> [b] -> [(a,b)]`. – Zeta Mar 14 '14 at 13:27
10

Another solution is to make a zip function that works on monoids and fills in the missing values with mempty:

import Data.Monoid

mzip :: (Monoid a, Monoid b) => [a] -> [b] -> [(a, b)]
mzip (a:as) (b:bs) = (a, b) : mzip as bs
mzip []     (b:bs) = (mempty, b) : mzip [] bs
mzip (a:as) []     = (a, mempty) : mzip as []
mzip _      _      = []

> mzip ["Line1","Line2","Line3"] ["Line4","Line5"]
[("Line1","Line4"),("Line2","Line5"),("Line3","")]
Reite
  • 1,677
  • 10
  • 12
  • 2
    @Riccardo I wouldn't say that it's way better, the accepted answer will work in situations when you don't necessarily have a `Monoid`, and may want to use different defaults for different zips. The data might not have a good meaning for `<>`, but would still have a sane `mempty` (or multiple sane defaults). Best practice in Haskell says to use the least restrictive type possible, using `Monoid` restricts this function more than necessary since `<>` is not used. Besides, `mzip` can be defined in terms of `zipWithPadding` by just passing `mempty` to both defaults. – bheklilr Mar 14 '14 at 13:04
  • @Riccardo I personally think it would be useful if the standard libraries defined a `class Default m where mempty :: m` and `class Default m => Monoid m where mappend :: m -> m -> m`. – bheklilr Mar 14 '14 at 13:08
  • 1
    @Riccardo: `mzip = zipWithPadding mempty mempty`. Just because something uses `Monoid` doesn't make it better or more powerful ;). – Zeta Mar 14 '14 at 13:23
  • I see my comment was definitely too short not to be misunderstood, let me clarify :) I agree on the fact that the `zipWithPadding` solution is more general, no doubt about that. But the question was specifically about lists of strings, as the OP explicitly asks for a padding with `""`. In this regard, a solution involving monoids is not only imho general enough with respect to the OP, but also simpler than having to specify two default arguments which will not change. That's why I like the `mempty` solution the most. – Riccardo T. Mar 14 '14 at 14:41
  • @Riccardo I agree that for the particular problem, the `Monoid` based solution is particularly applicable, maybe even desirable. I've actually given a vote to both this solution and Zeta's above, I think they're both great solutions. The distinction I wanted to make was that a `Monoid` is not always necessary to solve a problem in which you need a default value, so that doesn't mean that the monoidal solution is any better than the more general form, just an alternative that imposes a few additional restrictions which many not be easy to work around. – bheklilr Mar 14 '14 at 15:16
  • 1
    It looks like someone has already implemented a `Default` class in the `data-default`. You could instead write `mzip` with the signature `(Default a, Default b) => [a] -> [b] -> [(a, b)]` and replace `mempty` with `def`. It already has numerous instances for common types, so it would be my choice in this situation if I were already dependent on that package. – bheklilr Mar 14 '14 at 15:18
  • Why Monoids instead of MonadPlus (using mzero) ? – codeshot Aug 13 '17 at 19:30
1

An alternative implementation of Reite's solution, using higher order functions, just for fun. :) Possibly slower, though, since I guess the length functions will require additional traversals of the lists.

import Data.Monoid (mempty)

zipPad :: (Monoid a, Monoid b) => [a] -> [b] -> [(a,b)]
zipPad xs ys = take maxLength $ zip (pad xs) (pad ys)
    where
        maxLength = max (length xs) (length ys)
        pad v = v ++ repeat mempty
imladris
  • 1,091
  • 10
  • 9
1

I think it will be much simple for you if you are new one in programming in Haskell

         zip' :: [String] -> [String] ->[(String,String)]
         zip' [][] = []
         zip' (x:xs)[] = bmi x : zip' xs []
                   where bmi x = (x,"")
         zip' [](x:xs) = bmi x : zip' [] xs
                   where bmi x = ("",x)
         zip' (x:xs) (y:ys) = bmi x y : zip' xs ys
                   where bmi x y = (x,y)    
MIRMIX
  • 1,052
  • 2
  • 14
  • 40
1

Sometimes I don't want to pad my list. For instance, when I want to zip equal length lists only. Here is a general purpose solution, which maybe returns any extra values if one list is longer.

zipWithSave :: (a -> b -> c) -> [a] -> [b] -> ([c],Maybe (Either [a] [b]))
zipWithSave f    []     []  = ([],Nothing)
zipWithSave f    []     bs  = ([],Just (Right bs))
zipWithSave f    as     []  = ([],Just (Left as))
zipWithSave f (a:as) (b:bs) = (f a b : cs , sv)
  where (cs, sv) = zipWithSave f as bs

Using (zps,svs) = zipWithSave f as bs, svs can be one of three cases: Just (Left x) wherein leftovers from as are returned as x, Just (Right x) wherein leftovers from bs are returned, or Nothing in the case of equal length lists.

Another general purpose one is to just supply extra functions for each case.

zipWithOr :: (a -> b -> c) -> (a -> c) -> (b -> c) -> [a] -> [b] -> [c]
zipWithOr _ _  _     []    []   = []
zipWithOr _ _  fb    []     bs  = map fb bs
zipWithOr _ fa _     as     []  = map fa as
zipWithOr f fa fb (a:as) (b:bs) = (f a b) : zipWithOr f fa fb as bs

This is just an elaboration of Zeta's approach. That function is then implemented as (using {-# LANGUAGE TupleSections #-}):

zipWithPadding a b as bs = zipWithOr (,) (,b) (a,) as bs 
Trevor
  • 11
  • 1