将Scala列表转换为元组?

96

如何将一个包含三个元素的列表转换成大小为3的元组?

例如,假设我有val x = List(1, 2, 3),我想将其转换为(1, 2, 3)。我该怎么做?


1
据我所知,除非手动转换,否则不可能。给定def toTuple(x: List[Int]): R,R的类型应该是什么? - user166390
7
如果不需要对任意大小的元组执行操作(例如上面介绍的假设方法),考虑使用 x match { case a :: b :: c :: Nil => (a, b, c); case _ => (0, 0, 0) },注意结果类型固定为 Tuple3[Int,Int,Int] - user166390
2
虽然使用List无法实现您所需的功能,但您可以尝试使用Shapeless的HList类型,该类型允许元组之间的转换(http://github.com/milessabin/shapeless#facilities-for-abstracting-over-arity)。也许它适用于您的用例。 - user500592
14个回答

62

您可以使用Scala提取器和模式匹配(link)来实现:

val x = List(1, 2, 3)

val t = x match {
  case List(a, b, c) => (a, b, c)
}

返回一个元组

t: (Int, Int, Int) = (1,2,3)

此外,如果不确定列表的大小,您可以使用通配符运算符。
val t = x match {
  case List(a, b, c, _*) => (a, b, c)
}

4
在这个解决方案的背景下,关于类型安全的投诉意味着您可能会在运行时收到MatchError。如果您愿意,可以尝试捕获该错误并在List长度不正确时执行某些操作。此解决方案的另一个好处是输出不必是元组,它可以是您自己定义的自定义类或一些数字的转换形式。但我认为您无法对非列表执行此操作(因此您可能需要对输入执行“.toList”操作)。 - Jim Pivarski
你可以使用 +: 语法来处理任何类型的 Scala 序列。此外,不要忘记添加一个 catch-all。 - ig-dev

62

由于通常在运行时无法知道列表的长度,因此您无法以类型安全的方式完成此操作。但元组的“长度”必须编码在其类型中,并且因此在编译时已知。例如,(1,'a',true) 具有类型 (Int, Char, Boolean),这是 Tuple3[Int, Char, Boolean] 的语法糖。元组具有此限制的原因是它们需要能够处理非同构类型。


有没有一些固定大小的元素列表,它们具有相同的类型?当然不包括导入非标准库,比如shapeless。 - user445107
1
@davips 我不知道有没有现成的,但是自己写很容易,比如 case class Vec3[A](_1: A, _2: A, _3: A) - Tom Crockett
这将是一个由相同类型元素构成的“元组”,我无法对其应用 map 函数,例如。 - user445107
1
@davips 对于任何新的数据类型,您都需要定义它如何工作,例如 map 等。 - Tom Crockett
1
这种限制似乎更多地表明了类型安全的无用性,而不是其他什么。它让我想起了这句话:“空集合有很多有趣的属性。” - ely

49

一个使用shapeless的示例:

import shapeless._
import syntax.std.traversable._
val x = List(1, 2, 3)
val xHList = x.toHList[Int::Int::Int::HNil]
val t = xHList.get.tupled

注意:编译器需要一些类型信息将List转换为HList,这就是为什么您需要向toHList方法传递类型信息的原因。


18

Shapeless 2.0更改了一些语法。以下是使用shapeless的更新解决方案。

import shapeless._
import HList._
import syntax.std.traversable._

val x = List(1, 2, 3)
val y = x.toHList[Int::Int::Int::HNil]
val z = y.get.tupled

主要问题在于必须事先指定.toHList的类型。更一般地说,由于元组在其度量方面受到限制,您的软件设计可能会通过不同的解决方案得到更好的服务。

不过,如果您正在静态创建列表,请考虑使用此解决方案,同样使用shapeless。 在这里,我们直接创建一个HList,并且类型在编译时可用。请记住,HList具有来自List和Tuple类型的功能。即它可以有不同类型的元素,就像元组一样,并且可以映射等其他操作,就像标准集合一样。不过,需要花费一些时间才能熟悉HLists,因此如果您是新手,请小心谨慎。

scala> import shapeless._
import shapeless._

scala> import HList._
import HList._

scala>   val hlist = "z" :: 6 :: "b" :: true :: HNil
hlist: shapeless.::[String,shapeless.::[Int,shapeless.::[String,shapeless.::[Boolean,shapeless.HNil]]]] = z :: 6 :: b :: true :: HNil

scala>   val tup = hlist.tupled
tup: (String, Int, String, Boolean) = (z,6,b,true)

scala> tup
res0: (String, Int, String, Boolean) = (z,6,b,true)

15

尽管它很简单且不适用于任何长度的列表,但它是类型安全的,并且在大多数情况下都是正确的答案:

val list = List('a','b')
val tuple = list(0) -> list(1)

val list = List('a','b','c')
val tuple = (list(0), list(1), list(2))

另一种可能性是,当您不想命名列表或重复它时(我希望有人能展示一种避免Seq/head部分的方法):

val tuple = Seq(List('a','b')).map(tup => tup(0) -> tup(1)).head
val tuple = Seq(List('a','b','c')).map(tup => (tup(0), tup(1), tup(2))).head

14

顺便说一下,我想要一个元组来 初始化几个字段 ,并且想要使用元组赋值的语法糖。 例如:

FWIW,我想用元组来初始化多个字段,并且希望使用元组赋值的语法糖。

val (c1, c2, c3) = listToTuple(myList)

原来对于将列表内容赋值的操作,还有一种语法糖可用...

val c1 :: c2 :: c3 :: Nil = myList

如果你遇到相同的问题,就不需要使用元组。


1
@javadba 我本来在寻找如何实现listToTuple(),但最终发现不需要了。列表语法糖解决了我的问题。 - Peter L
1
还有一种语法,我认为看起来更好一些:val List(c1, c2, c3) = myList - Kolmar

9
如果你非常确定你的list.size<23,那么可以使用它:
def listToTuple[A <: Object](list:List[A]):Product = {
  val class = Class.forName("scala.Tuple" + list.size)
  class.getConstructors.apply(0).newInstance(list:_*).asInstanceOf[Product]
}
listToTuple: [A <: java.lang.Object](list: List[A])Product

scala> listToTuple(List("Scala", "Smart"))
res15: Product = (Scala,Smart)

8
你无法以类型安全的方式这样做。在Scala中,列表是某种类型元素的任意长度序列。就类型系统而言,x可以是任意长度的列表。
相比之下,元组的元数必须在编译时已知。允许将x赋值给元组类型将违反类型系统的安全保证。
事实上,由于技术原因,Scala元组被限制为22个元素,但在2.11版本中不再有此限制。Case class的限制在2.11中被解除 https://github.com/scala/scala/pull/2305 可以手动编写一个函数,将最多22个元素的列表转换,并对更大的列表抛出异常。Scala的模板支持是即将推出的功能,可以使此过程更简洁。但这将是一个丑陋的hack。

这个假设函数的结果类型会是 Any 吗? - user445107
案例类(case class)在2.11版中的限制已被取消 https://github.com/scala/scala/pull/2305 - gdoubleod

6

使用Sized可以在shapeless中更少地使用样板代码来完成此操作:

scala> import shapeless._
scala> import shapeless.syntax.sized._

scala> val x = List(1, 2, 3)
x: List[Int] = List(1, 2, 3)

scala> x.sized(3).map(_.tupled)
res1: Option[(Int, Int, Int)] = Some((1,2,3))

它是类型安全的:如果元组大小不正确,则会得到None,但元组大小必须是字面量或final val(可转换为shapeless.Nat)。


这似乎对大于22的尺寸不起作用,至少对于shapeless_2.11:2.3.3是如此。 - kfkhalili
@kfkhalili 是的,这不是一个无形的限制。Scala 2本身不允许具有超过22个元素的元组,因此无法使用任何方法将更大尺寸的列表转换为元组。 - Kolmar

6

使用模式匹配:

val intTuple = List(1,2,3) match {case List(a, b, c) => (a, b, c)}

当回答一个这么老的问题(超过6年)时,通常最好指出你的答案与已提交的所有(12个)旧答案不同之处。特别是,只有在List/Tuple的长度是已知常量的情况下,你的答案才适用。 - jwvh
谢谢@jwvh,我会考虑你的评论并在以后的工作中加以应用。 - Michael Olafisoye

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接