views:

369

answers:

4

I know that Scala's Lists have a map implementation with signature (f: (A) => B):List[B] and a foreach implementation with signature (f: (A) => Unit):Unit but I'm looking for something that accepts multiple iterables the same way that the Python map accepts multiple iterables.

I'm looking for something with a signature of (f: (A,B) => C, Iterable[A], Iterable[B] ):Iterable[C] or equivalent. Is there a library where this exists or a comparable way of doing similar?

Edit:

As suggested below I could do

val output = myList zip( otherList ) map( x => x(0) + x(1) )

but that creates a temporary list in between steps. If the commentor would post I could upvote him (hint, hint) but is there another way?

+6  A: 

In scala 2.8, there is a method called zipped in Tuple2 & Tuple3 which avoid to create temporary collection. Here is some sample use case:

Welcome to Scala version 2.8.0.r21561-b20100414020114 (Java HotSpot(TM) Client VM, Java 1.6.0_18).
Type in expressions to have them evaluated.
Type :help for more information.

scala> val xs = 0 to 9
xs: scala.collection.immutable.Range.Inclusive with scala.collection.immutable.Range.ByOne = Range(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

scala> val ys = List.range(0,10)
ys: List[Int] = List(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

scala> val zs = Array.range(0,10)
zs: Array[Int] = Array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9)

scala> (xs,ys).zipped.map{ _+_ }
res1: scala.collection.immutable.IndexedSeq[Int] = Vector(0, 2, 4, 6, 8, 10, 12, 14, 16, 18)

scala> (zs,ys,xs).zipped.map{ _+_+_ }
res2: Array[Int] = Array(0, 3, 6, 9, 12, 15, 18, 21, 24, 27)

scala>

There is a zip method in both Tuple2 and Tuple3. xs.zip(ys) is the same as (xs,ys).zip

Note: There is also some shortage in (xs,ys).zip and (xs,ys).zipped, make sure that xs can't be a INFINITE Stream. Go to Ticket #2634 for more information. I have a post in nabble.com some days ago which shows my opinions about how to fix this ticket.

Eastsun
+3  A: 

There is a method map2 in the List object in Scala 2.7 (and 2.8, but it's deprecated in favor of zipped). You use it like so:

List.map2( List(1,2,3) , List(4,5,6) ) { _ * _ }  // Gives List(4,10,18)

Eastsun's already shown how to use zipped in 2.8 (which works on all collections, not just lists).

Rex Kerr
I appreciate the 2.7 comment. I'll be transitioning to 2.8 soon enough.
wheaties
+2  A: 

Well, I don't know the syntax (f: (A,B) => C, Iterable[A], Iterable[B] ):Iterable[C] (and I know nothing of Scala), but if I had to guess, it would mean "A function f taking two iterable arguments A and B and returning an iterable C". I'm not sure if this implies that all iterables yield the same number of items.

In Python, I think you're looking for the zip function:

>>> A = range(10, 15)
>>> B = range(1000, 1500, 100)
>>> zip(A, B)
[(10, 1000), (11, 1100), (12, 1200), (13, 1300), (14, 1400)]
>>> [a + b for a,b in zip(A, B)]
[1010, 1111, 1212, 1313, 1414]

zip's output is only as long as the shortest iterable:

>>> A=range(10, 12)
>>> zip(A, B)
[(10, 1000), (11, 1100)]

Anyway, some built-in Python functions everyone needs to know but easily misses: enumerate, map, reduce, and zip. filter used to be on that list, but it's clearer and more flexible to use a list comprehension these days.

Mike DeSimone
+6  A: 

The function you're looking for is usually called zipWith. It's unfortunately not provided in the standard libraries, but it's pretty easy to write:

def zipWith[A,B,C](f: (A,B) => C, a: Iterable[A], b: Iterable[B]) =
  new Iterable[C] {
    def elements = (a.elements zip b.elements) map f.tupled
  }

This will traverse only once, since the implementations for zip and map on iterators are fully lazy.

But why stop at Iterable? This has an even more general form. We could declare an interface for all data structures that can be zipped this way.

trait Zip[F[_]] {
  def zipWith[A,B,C](f: (A,B) => C, a: F[A], b: F[B]): F[C]
}

For example, we can zip functions:

trait Reader[A] {
  type Read[B] = (A => B)
}

def readerZip[T] = new Zip[Reader[T]#Read] {
  def zipWith[A,B,C](f: (A,B) => C, a: T => A, b: T => B): T => C =
    (t: T) => f(a(t),b(t))
}

There turns out to be an even more general expression of this type. In general, type constructors that allow an implementation of this interface are applicative functors

trait Applicative[F[_]] {
  def pure[A](a: A): F[A]
  def map[A,B](f: A => B, a: F[A]): F[B]
  def ap[A,B](f: F[A => B], a: F[A]): F[B]
}

An implementation of zipWith is then just this:

def zipWith[F[_],A,B,C](f: A => B => C, a: F[A], b: F[B])
                       (implicit m: Applicative[F]) =
  m.ap(m.map(f,a), b)

This generalises to functions of any arity:

  m.ap(m.ap(m.ap(m.map(f,a), b), c), d)

The Scalaz library provides Applicative instances for a lot of data structures in the standard library. It also does library pimping so that you can just do this:

import scalaz._
import Scalaz._

List(1,2,3).zipWith(List(2,3,4))(x: Int, y: Int) => x * y)

Also, convenient syntax is provided for ap. In Scalaz, this function is called <*>:

def zipWith[F[_]:Applicative,A,B,C](f: A => B => C, a: F[A], b: F[B]) =
  (a map f) <*> b
Apocalisp
Thank you. I have so very much to learn. This was very educational.
wheaties