28

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?

Community
  • 1
  • 1
ForceOfWill
  • 435
  • 1
  • 6
  • 13

2 Answers2

54

The method you are looking for is .zipAll:

scala> val list1 = List("a", "b", "c")
list1: List[String] = List(a, b, c)

scala> val list2 = List("x", "y")
list2: List[String] = List(x, y)

scala> list1.zipAll(list2, "", "")
res0: List[(String, String)] = List((a,x), (b,y), (c,""))

.zipAll takes 3 arguments:

  • the iterable to zip with
  • the default value if this (the collection .zipAll is called on) is shorter
  • the default value if the other collection is shorter
Marth
  • 23,920
  • 3
  • 60
  • 72
  • Thanks! Now it works with this solution: `list1.zipAll(list2, "", "") map ({ case (y, x) => y + x })` but not like before with `list1.zipAll(list2, "", "") map (_ + _)`. Why can't I use the short syntax with the zipAll like the zipped function? – ForceOfWill Feb 15 '16 at 11:52
  • 1
    @ForceOfWill: `(list1, list2).zipped` returns a `Tuple2Zipped`, where [`.map`](http://www.scala-lang.org/api/current/index.html#scala.runtime.Tuple2Zipped@map[B,To](f:(El1,El2)=>B)(implicitcbf:scala.collection.generic.CanBuildFrom[Repr1,B,To]):To) takes a `f:(El1,El2)=>B` (ie a function that takes 2 arguments). `List[A]`'s `.map` takes a `f: A => B`, ie only 1 argument (which in this case is a `Tuple2`). Unfortunately this means that `_ + _` (which is a function that takes 2 arguments) can't be applied here. – Marth Feb 15 '16 at 12:46
  • The explaination for the three arguments was the best part. Thanks – Shivam Sahil Dec 01 '21 at 11:47
6

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.

elm
  • 20,117
  • 14
  • 67
  • 113