N-zip using unfold

We can implement the zip method using unfold as follows:

 def myZip(a: List[Int], b: List[Int]) ={
      List.unfold(a, b){
      case(a, b) if a.length > 0 & b.length >0 => Some( (a.head, b.head), (a.tail, b.tail))
      case _ => None
      }
     }

It is easy to generalize this method to any finite number of lists to be zipped. But I cannot come up with an idea how to define a general zip function operating on unspecified in advance number of lists. Can you please give some hints?

The problem with generalisation is the output type, a zipN method would return a List[List[Any]] or a List[List[A]] the first one is not really useful, and the second one is usually called transpose instead.

BTW, you should prefer pattern matching there:

def myZip[A, B](as: List[A], bs: List[B]): List[(A, B)] =
  List.unfold((as, bs)) {
    case (a :: tailA, b :: tailB) =>
      Some((a, b), (tailA, tailB))

    case _ =>
      None
  }
3 Likes

@BalmungSan Thanks a lot, really useful!

But how can I use transpose here
(List(1,2,3), List("a","b","c"), List(1.0, 2.0, 3.0)).transpose

Is patter matching serves any other purpose besides replacing my awkward length check?

It makes the code prettier (subjective) and arguably safer (objective)

Well, you can’t.
Frist things first, transponse is not a method on tuples, is a method on List (or any other collection).
Second, in that case, your result would be a List[List[Any]] which as I said, is not really useful at all.
You can see the implementation of such method here: Scastie - An interactive playground for Scala.

That is why I said that an arbitrary and useful zipN in current Scala is not simple to implement.
Alternatives could be, using source generators to generate enough overloads for most use cases; this is what cats does, or exploring a typeclass approach with implicit derivation for any tuple.

3 Likes

You are amazing, thanks for the code!

By the way, in your resulting type parametrization you probably have to use List[(A, B)], i.e., list of tuples (at least in scala 2)

Where? In the implementation of transponse? Again, not possible, that is all I have been saying all along.

In the code that you posted above

def myZip[A, B](as: List[A], bs: List[B]): List[A, B] =
  List.unfold((as, bs)) {
    case (a :: tailA, b :: tailB) =>
      Some((a, b), (tailA, tailB))

    case _ =>
      None
  }

It is a minor thing, it just gives an error with List[A, B] and works fine with List[(A, B)].

Oh, ah, right; sorry for that, just fixed it :slight_smile:
Thanks!

1 Like

Hi @yarchik, FWIW, just in case you find them of interest, here are some other alternatives:

def zip[A,B](l1: List[A], l2: List[B]): List[(A,B)] = (l1,l2) match
  case (a :: as, b :: bs) => (a,b) :: zip(as,bs)
  case _ => Nil

def zip[A,B](as: List[A], bs: List[B]): List[(A,B)] = 
  if as.isEmpty || bs.isEmpty then Nil
  else (as.head,bs.head)::zip(as.tail,bs.tail)
1 Like