Scala中有没有类似于Python更通用的map函数的实现?

9 投票
4 回答
3284 浏览
提问于 2025-04-15 21:40

我知道Scala的列表有一个叫做 map 的功能,它的用法是 (f: (A) => B):List[B],还有一个叫做 foreach 的功能,用法是 (f: (A) => Unit):Unit。不过我想找一个可以接受多个可迭代对象的功能,就像Python的 map 可以接受多个可迭代对象一样。

我想要的功能的用法是 (f: (A,B) => C, Iterable[A], Iterable[B] ):Iterable[C] 或者类似的。有没有这样的库,或者有什么其他的方法可以实现类似的功能呢?

编辑:

正如下面所建议的,我可以这样做:

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

但是这样会在步骤之间创建一个临时列表。如果评论的人能发个帖子,我可以给他点赞(提示,提示),但还有其他方法吗?

4 个回答

3

在Scala 2.7(还有2.8,不过在2.8中它被标记为不推荐使用,建议用zipped替代)中,List对象里有一个方法叫map2。你可以这样使用它:

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

Eastsun已经展示了如何在2.8中使用zipped(这个方法适用于所有集合,不仅仅是列表)。

11

你要找的这个功能通常叫做 zipWith。可惜的是,它在标准库里没有提供,但其实写起来很简单:

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
  }

这个函数只会遍历一次,因为在迭代器上实现的 zipmap 都是完全懒惰的,也就是说它们只在需要的时候才会计算。

但是,为什么要止步于 Iterable 呢?这个功能还有更通用的形式。我们可以为所有可以这样“压缩”的数据结构声明一个接口。

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

比如,我们可以把函数也进行“压缩”:

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))
}

实际上,这种类型还有一个更通用的表达方式。一般来说,允许实现这个接口的类型构造器被称为 应用函子

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]
}

然后,zipWith 的实现就是这样:

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)

这个可以推广到任何参数数量的函数:

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

Scalaz 库为标准库中的许多数据结构提供了应用函子的实例。此外,它还为 ap 提供了方便的语法。在 Scalaz 中,这个函数叫做 <*>

def zipWith[F[_]:Applicative,A,B,C](f: A => B => C, a: F[A], b: F[B]) =
  (a map f) <*> b
12

在Scala 2.8中,有一个叫做zipped的方法,适用于Tuple2和Tuple3,这个方法可以避免创建临时集合。

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>

Tuple2和Tuple3都有一个zip方法。xs.zip(ys)和(xs, ys).zip是一样的。

注意:在(xs, ys).zip和(xs, ys).zipped中也有一些不足之处,确保xs不能是一个无限的流。想了解更多信息,可以查看Ticket #2634。几天前我在nabble.com上发过一篇帖子,里面分享了我对如何解决这个问题的看法。

撰写回答