5
\$\begingroup\$

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) => {
        val (tails, heads) = z
        (a.tail :: tails, a.head :: heads)
      })
      if (left.foldLeft(false)(_ || _.isEmpty))
        f(zipped.reverse) :: acc
      else
        loop(f(zipped.reverse) :: acc, left.reverse)
    }

    loop(Nil, lists).reverse
  }
\$\endgroup\$
5
  • \$\begingroup\$ Can you explain us more about what you are exactly trying to accomplish with your code? Please edit it into the question. \$\endgroup\$
    – skiwi
    Commented Nov 28, 2014 at 10:44
  • \$\begingroup\$ @skiwi: Added some explanation. Hopefully the test will make it clear.. \$\endgroup\$ Commented Nov 28, 2014 at 11:04
  • 1
    \$\begingroup\$ 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? \$\endgroup\$ Commented Nov 28, 2014 at 19:18
  • \$\begingroup\$ @AkosKrivachy post an answer with that! \$\endgroup\$
    – janos
    Commented Nov 28, 2014 at 20:13
  • \$\begingroup\$ @AkosKrivachy: No that will result in List(1::2::3, 4::5::6, 7::8::9) \$\endgroup\$ Commented Nov 29, 2014 at 0:31

2 Answers 2

3
\$\begingroup\$

How about this?

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)
\$\endgroup\$
1
\$\begingroup\$

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] = {
  lists.headOption.map(_.size) match {
    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)
  }
}
\$\endgroup\$
4
  • 1
    \$\begingroup\$ 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") \$\endgroup\$ Commented Nov 29, 2014 at 0:31
  • \$\begingroup\$ @BasilTomato Thank you for pointing that out. Updated my answer with an actual solution. \$\endgroup\$ Commented Nov 29, 2014 at 1:10
  • \$\begingroup\$ 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 \$\endgroup\$ Commented Dec 2, 2014 at 1:36
  • \$\begingroup\$ Why not match on the headOption and then use the .size on the Some? \$\endgroup\$ Commented Dec 2, 2014 at 1:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.