我可以在 Scala 中将两个以上的列表压缩在一起吗?

给出以下 Scala 列表:

val l = List(List("a1", "b1", "c1"), List("a2", "b2", "c2"), List("a3", "b3", "c3"))

我怎样才能得到:

List(("a1", "a2", "a3"), ("b1", "b2", "b3"), ("c1", "c2", "c3"))

由于 zip 只能用于组合两个 List,我认为您需要以某种方式迭代/减少主 List。毫不奇怪,以下方法并不奏效:

scala> l reduceLeft ((a, b) => a zip b)
<console>:6: error: type mismatch;
found   : List[(String, String)]
required: List[String]
l reduceLeft ((a, b) => a zip b)

有什么建议吗? 我想我错过了一个非常简单的方法。

更新: 我正在寻找一种解决方案,它可以接受一个包含 N 个 List 和 M 个元素的 List,并创建一个包含 M 个 TupleNs 的 List。

更新2: 事实证明,对于我的特定用例来说,拥有一个列表列表比拥有一个元组列表更好,所以我接受南瓜的回应。它也是最简单的,因为它使用本机方法。

41055 次浏览

Yes, with zip3.

Scala treats all of its different tuple sizes as different classes (Tuple1, Tuple2, Tuple3, Tuple4,...,Tuple22) while they do all inherit from the Product trait, that trait doesn't carry enough information to actually use the data values from the different sizes of tuples if they could all be returned by the same function. (And scala's generics aren't powerful enough to handle this case either.)

Your best bet is to write overloads of the zip function for all 22 Tuple sizes. A code generator would probably help you with this.

I don't believe it's possible to generate a list of tuples of arbitrary size, but the transpose function does exactly what you need if you don't mind getting a list of lists instead.

I don't believe that's possible without being repetitive. For one simple reason: you can't define the returning type of the function you are asking for.

For instance, if your input was List(List(1,2), List(3,4)), then the return type would be List[Tuple2[Int]]. If it had three elements, the return type would be List[Tuple3[Int]], and so on.

You could return List[AnyRef], or even List[Product], and then make a bunch of cases, one for each condition.

As for general List transposition, this works:

def transpose[T](l: List[List[T]]): List[List[T]] = l match {
case Nil => Nil
case Nil :: _ => Nil
case _ => (l map (_.head)) :: transpose(l map (_.tail))
}
scala> (List(1,2,3),List(4,5,6),List(7,8,9)).zipped.toList
res0: List[(Int, Int, Int)] = List((1,4,7), (2,5,8), (3,6,9))

For future reference.

transpose does the trick. A possible algorithm is:

def combineLists[A](ss:List[A]*) = {
val sa = ss.reverse;
(sa.head.map(List(_)) /: sa.tail)(_.zip(_).map(p=>p._2 :: p._1))
}

For example:

combineLists(List(1, 2, 3), List(10,20), List(100, 200, 300))
// => List[List[Int]] = List(List(1, 10, 100), List(2, 20, 200))

The answer is truncated to the size of the shortest list in the input.

combineLists(List(1, 2, 3), List(10,20))
// => List[List[Int]] = List(List(1, 10), List(2, 20))

So this piece of code won't answer the needs of the OP, and not only because this is a four year old thread, but it does answer the title question, and perhaps someone may even find it useful.

To zip 3 collections:

as zip bs zip cs map {
case ((a,b), c) => (a,b,c)
}

product-collections has aflatZip operation up to arity 22.

scala> List(1,2,3) flatZip Seq("a","b","c") flatZip Vector(1.0,2.0,3.0) flatZip Seq(9,8,7)
res1: com.github.marklister.collections.immutable.CollSeq4[Int,String,Double,Int] =
CollSeq((1,a,1.0,9),
(2,b,2.0,8),
(3,c,3.0,7))

With Scalaz:

import scalaz.Zip
import scalaz.std.list._


// Zip 3
Zip[List].ap.tuple3(List("a1", "b1"),
List("a2", "b2"),
List("a3", "b3"))


// Zip 4
Zip[List].ap.tuple4(List("a1", "b1"),
List("a2", "b2"),
List("a3", "b3"),
List("a4", "b4"))


// Zip 5
Zip[List].ap.tuple5(List("a1", "b1"),
List("a2", "b2"),
List("a3", "b3"),
List("a4", "b4"),
List("a5", "b5"))

For more than 5:

// Zip 6
Zip[List].ap.apply6(List("a1", "b1"),
List("a2", "b2"),
List("a3", "b3"),
List("a4", "b4"),
List("a5", "b5"),
List("a6", "b6"))((_, _, _, _, _, _))


// Zip 7
Zip[List].ap.apply7(List("a1", "b1"),
List("a2", "b2"),
List("a3", "b3"),
List("a4", "b4"),
List("a5", "b5"),
List("a6", "b6"),
List("a7", "b7"))((_, _, _, _, _, _, _))


...


// Zip 12
Zip[List].ap.apply12(List("a1", "b1"),
List("a2", "b2"),
List("a3", "b3"),
List("a4", "b4"),
List("a5", "b5"),
List("a6", "b6"),
List("a7", "b7"),
List("a8", "b8"),
List("a9", "b9"),
List("a10", "b10"),
List("a11", "b11"),
List("a12", "b12"))((_, _, _, _, _, _, _, _, _, _, _, _))

If you don't want to go down the applicative scalaz/cats/(insert your favourite functional lib here) route, pattern matching is the way to go, although the (_, _) syntax is a bit awkward with nesting, so let's change it:

import scala.{Tuple2 => &}


for (i1 & i2 & i3 & i4 <- list1 zip list2 zip list3 zip list4) yield (i1, i2, i3, i4)

The & is an arbitrary choice here, anything that looks nice infix should do it. You'll likely get a few raised eyebrows during code review, though.

It should also work with anything you can zip (e.g. Futures)

Scala 2.12.13 and below

If you know how long the input List is, you can join the list into a Tuple and use Tuple's .zipped method:

val l = List(List("a1", "b1", "c1"), List("a2", "b2", "c2"), List("a3", "b3", "c3"))


println(l match {
case l1::l2::l3::_ => (l1,l2,l3).zipped.toList
case _ => throw new IllegalArgumentException("List is not the right length")
}) // List((a1,a2,a3), (b1,b2,b3), (c1,c2,c3))

Scastie Example - 2.12.13

>= Scala 2.13

The above solution is deprecated - use lazyZip instead:

val l = List(List("a1", "b1", "c1"), List("a2", "b2", "c2"), List("a3", "b3", "c3"))


println(l match {
case l1::l2::l3::_ => (l1 lazyZip l2 lazyZip l3).toList
case _ => throw new IllegalArgumentException("List is not the right length")
}) // List((a1,a2,a3), (b1,b2,b3), (c1,c2,c3))

Scastie Example - 2.13.0