# zipWith function in Scala

My implementation works, but it looks very ugly. Curious to see how it's better implemented.

The zipWith function takes a list of lists (List[List[A]]), or a row with columns if you like. It will then transform this into a column with rows and applies a function List[A]=>B to obtain a List[B]. Hopefully the unit test below explains this better.

  test("zipWith"){
val input = List(List(1,2,3), List(4,5,6), List(7,8,9))
val expected = List("1::4::7", "2::5::8", "3::6::9")
assert(zipWith(input)(_.mkString("::")) === expected)
}


and the implementation is:

  def zipWith[A, B](lists: List[List[A]])(f: List[A] => B): List[B] = {
@tailrec
def loop(acc: List[B], input: List[List[A]]): List[B] = {
val init = (Nil: List[List[A]], Nil: List[A])
val (left, zipped) = input.foldLeft(init)((z, a) => {
})
if (left.foldLeft(false)(_ || _.isEmpty))
f(zipped.reverse) :: acc
else
loop(f(zipped.reverse) :: acc, left.reverse)
}

loop(Nil, lists).reverse
}

• Can you explain us more about what you are exactly trying to accomplish with your code? Please edit it into the question. – skiwi Nov 28 '14 at 10:44
• @skiwi: Added some explanation. Hopefully the test will make it clear.. – BasilTomato Nov 28 '14 at 11:04
• You do realize that the code can be replaced with this and will achieve the same thing: def zipWith[A, B](lists: List[List[A]])(f: List[A] => B): List[B] = lists.map(f)? Are you looking at improving your style? – Akos Krivachy Nov 28 '14 at 19:18
• @AkosKrivachy post an answer with that! – janos Nov 28 '14 at 20:13
• @AkosKrivachy: No that will result in List(1::2::3, 4::5::6, 7::8::9) – BasilTomato Nov 29 '14 at 0:31

def zipWith[A, B](ts: Traversable[Traversable[A]])(f: Traversable[A] => B): Traversable[B] = {
ts.transpose.map(f)
}


Example:

scala> zipWith(input)(_.mkString("::"))
res10: Traversable[String] = List(1::4::7, 2::5::8, 3::6::9)


Two things:

• Your variable names could be better, I would rather use rows and columns as a reader can understand it easier.
• List is an implementation of Seq, you should probably use Seq where you can.

With that said I would do something like this:

def zipWith[A, B](lists: Seq[Seq[A]])(f: Seq[A] => B): Seq[B] = {
case None =>
// Input list is empty, return empty
Seq.empty[B]
case Some(columnCount) =>
// Do a sanity check, based on column length of first row.
require(lists.forall(_.size == columnCount), "Can't zip rows that have differing column lengths.")
val initial = Seq.fill(columnCount)(Seq.empty[A])
val zippedByColumns = lists.foldLeft(initial) {
case (columns, nextRow) =>
columns.zip(nextRow).map{ case (column, rowElement) => column :+ rowElement }
}
zippedByColumns.map(f)
}
}

• Nope that will result in List(1::2::3, 4::5::6, 7::8::9). It needs to be List("1::4::7", "2::5::8", "3::6::9") – BasilTomato Nov 29 '14 at 0:31
• @BasilTomato Thank you for pointing that out. Updated my answer with an actual solution. – Akos Krivachy Nov 29 '14 at 1:10
• I've seen it stated that one should prefer Vector to List as a concrete instantiation of Seq (but obviously use Seq as arguments where possible) as Vector ends up being better at things List should be better at anyways – geoffjentry Dec 2 '14 at 1:36
• Why not match on the headOption and then use the .size on the Some? – geoffjentry Dec 2 '14 at 1:38