'Zip two lists of different lengths with default element to fill

Assume we have the following lists of different size:

val list1 = ("a", "b", "c")
val list2 = ("x", "y")

Now I want to merge these 2 lists and create a new list with the string elements being concatenated:

val desiredResult = ("ax", "by", "c")

I tried

val wrongResult = (list1, list2).zipped map (_ + _)

as proposed here, but this doesn't work as intended, because zip discards those elements of the longer list that can't be matched.

How can I solve this problem? Is there a way to zip the lists and give a "default element" (like the empty string in this case) if one list is longer?



Solution 1:[1]

The API-based zipAll is the way to go, yet you can implement it (as an exercise) for instance as follows,

implicit class OpsSeq[A,B](val xs: Seq[A]) extends AnyVal {
  def zipAll2(ys: Seq[B], xDefault: A, yDefault: B) = {
    val xs2 = xs ++ Seq.fill(ys.size-xs.size)(xDefault)
    val ys2 = ys ++ Seq.fill(xs.size-ys.size)(yDefault)
    xs2.zip(ys2) 
  }
}

Hence for instance

Seq(1,2).zipAll2(Seq(3,4,5),10,20)
List((1,3), (2,4), (10,5))

and

list1.zipAll2(list2, "", "")
List((a,x), (b,y), (c,""))

A recursive version,

def zipAll3[A,B](xs: Seq[A], ys: Seq[B], xd: A, yd: B): Seq[(A,B)] = {
  (xs,ys) match {
    case (Seq(),    Seq())    => Seq()
    case (x +: xss, Seq())    => (x,yd) +: zipAll3(xss, Seq(), xd, yd)
    case (Seq(),    y +: yss) => (xd,y) +: zipAll3(Seq(), yss, xd, yd)
    case (x +: xss, y +: yss) => (x,y) +: zipAll3(xss, yss, xd, yd) 
  }
}

with default xd and default yd values.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1