91

如何将具有(例如)3 个元素的列表转换为大小为 3 的元组?

例如,假设我有val x = List(1, 2, 3)并且我想将其转换为(1, 2, 3). 我怎样才能做到这一点?

4

13 回答 13

62

您可以使用 scala 提取器和模式匹配(链接)来做到这一点:

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)
}
于 2014-02-14T00:09:25.537 回答
61

您不能以类型安全的方式执行此操作。为什么?因为通常我们直到运行时才能知道列表的长度。但是元组的“长度”必须以它的类型编码,因此在编译时就知道了。例如,(1,'a',true)有类型(Int, Char, Boolean),它是糖Tuple3[Int, Char, Boolean]。元组有这个限制的原因是它们需要能够处理非同质类型。

于 2013-02-06T06:31:37.567 回答
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

注意:编译器需要一些类型信息来转换 HList 中的 List ,所以需要将类型信息传递给toHList方法

于 2013-02-06T11:29:23.967 回答
19

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)
于 2013-11-11T07:51:05.967 回答
14

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

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
于 2013-08-25T05:30:18.103 回答
13

FWIW,我想要一个元组来初始化一些字段,并想使用元组赋值的语法糖。例如:

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

事实证明,还有用于分配列表内容的语法糖......

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

因此,如果您遇到相同的问题,则不需要元组。

于 2015-01-29T03:38:08.327 回答
8

如果您非常确定您的 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)
于 2017-05-31T17:44:53.257 回答
7

您不能以类型安全的方式执行此操作。在 Scala 中,列表是某种类型元素的任意长度序列。据类型系统所知,x可以是任意长度的列表。

相反,元组的数量必须在编译时知道。x允许分配给元组类型将违反类型系统的安全保证。

事实上,由于技术原因,Scala 元组被限制为 22 个元素,但在 2.11 中不再存在限制,在 2.11 中已经取消了案例类限制https://github.com/scala/scala/pull/2305

可以手动编写一个函数来转换最多 22 个元素的列表,并为更大的列表抛出异常。Scala 的模板支持(即将推出的功能)将使这更加简洁。但这将是一个丑陋的黑客。

于 2013-02-06T06:31:21.100 回答
6

使用模式匹配:

val intTuple = List(1,2,3) match {case List(a, b, c) => (a, b, c)}
于 2019-05-06T09:33:35.430 回答
5

这也可以shapeless使用更少的样板来完成Sized

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

于 2017-06-22T18:13:16.800 回答
2

2015 年发布。为了让Tom Crockett 的回答 更加清晰,这里有一个真实的例子。

起初,我对此感到困惑。因为我来自 Python,你可以在那里做tuple(list(1,2,3)).
是否缺少 Scala 语言?(答案是——这不是关于 Scala 或 Python,而是关于静态类型和动态类型。)

这使我试图找到 Scala 不能做到这一点的症结所在。


下面的代码示例实现了一个toTuple方法,该方法具有 type-safetoTupleN和 type-unsafe toTuple

toTuple方法在运行时获取类型长度信息,即在编译时没有类型长度信息,因此返回类型确实Product很像Python的tuple(每个位置没有类型,也没有类型长度)。
这种方式容易出现运行时错误,例如类型不匹配或IndexOutOfBoundException. (所以 Python 方便的 list-to-tuple 并不是免费的午餐。)

相反,使toTupleN编译时安全的是用户提供的长度信息。

implicit class EnrichedWithToTuple[A](elements: Seq[A]) {
  def toTuple: Product = elements.length match {
    case 2 => toTuple2
    case 3 => toTuple3
  }
  def toTuple2 = elements match {case Seq(a, b) => (a, b) }
  def toTuple3 = elements match {case Seq(a, b, c) => (a, b, c) }
}

val product = List(1, 2, 3).toTuple
product.productElement(5) //runtime IndexOutOfBoundException, Bad ! 

val tuple = List(1, 2, 3).toTuple3
tuple._5 //compiler error, Good!
于 2015-04-22T05:04:37.783 回答
2

你也可以这样做

  1. 通过模式匹配(你不想要的)或
  2. 通过遍历列表并一个一个地应用每个元素。

    val xs: Seq[Any] = List(1:Int, 2.0:Double, "3":String)
    val t: (Int,Double,String) = xs.foldLeft((Tuple3[Int,Double,String] _).curried:Any)({
      case (f,x) => f.asInstanceOf[Any=>Any](x)
    }).asInstanceOf[(Int,Double,String)]
    
于 2016-03-16T19:07:09.843 回答
0

只要你有类型:

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

def doSomething(a:Int *)

doSomething(x:_*)
于 2013-10-15T02:12:42.930 回答