将Scala列表转换为元组?


87

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

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


1
(“手动”除外)AFAIK是不可能的。给定def toTuple(x: List[Int]): R,R的类型应该是什么?

7
如果不需要对任意大小的元组执行此操作(即,类似于上面介绍的假设方法),请考虑x match { case a :: b :: c :: Nil => (a, b, c); case _ => (0, 0, 0) }并注意生成的类型固定为Tuple3[Int,Int,Int]

2
尽管您尝试使用做不到的事情List,但您可以查看Shapeless的HList类型,该类型允许在元组之间进行转换(github.com/milessabin/…)。也许适用于您的用例。

Answers:


59

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


是否存在一些具有相同类型的固定大小的元素列表?当然,不导入非标准库,例如无形状库。

1
@davips不是我所知道的,但是创建自己的东西很容易,例如case class Vec3[A](_1: A, _2: A, _3: A)
Tom Crockett 2014年

这将是具有相同类型的元素的“元组”,例如,我无法在其上应用map。

1
@davips与任何新数据类型一样,您必须定义mapetc的工作方式
Tom Crockett 2014年

1
这种限制似乎是类型安全无用性的响亮指标,比其他任何事情都重要。它使我想起了“空集具有许多有趣的属性”的说法。
ely

58

您可以使用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。如果需要,可以尝试捕获该错误,并在列表长度错误的情况下执行某些操作。此解决方案的另一个不错的功能是,输出不必是元组,它可以是您自己的自定义类之一,也可以是数字的某种转换。我不认为您可以对非列表执行此操作(因此您可能需要对输入执行.toList操作)。
Jim Pivarski 2014年

您可以使用+:语法对任何类型的Scala序列执行此操作。另外,别忘了添加一个包罗万象的内容
ig-dev

48

一个使用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方法的原因


18

Shapeless 2.0更改了一些语法。这是使用无形状的更新解决方案。

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类型的功能。也就是说,它可以具有不同类型的元素(如元组),并且可以映射到其他操作(如标准集合)中。HList会花一些时间来适应,但是如果您是新手,那么要慢慢走。

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)

13

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

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

10

FWIW,我想要一个元组来初始化多个字段,并且想要使用元组分配的语法糖。例如:

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

事实证明,也有语法糖可以分配列表的内容...

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

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


@javadba我一直在寻找如何实现listToTuple(),但最终不需要这样做。列表语法糖解决了我的问题。
彼得L

还有另一种语法,在我看来看起来更好一些:val List(c1, c2, c3) = myList
Kolmar

7

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

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

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

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


该假设函数的结果类型是否为“任何”?


5

如果您非常确定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)

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


这似乎不适用于大于22的尺寸,至少对于
shapeless_2.11

@kfkhalili是的,这不是一个无限制的限制。Scala 2本身不允许包含超过22个元素的元组,因此无法使用任何方法将较大尺寸的List转换为Tuple。
科尔玛

4

使用模式匹配:

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


回答这么老(超过6年)的问题时,通常最好指出您的答案与已提交的所有(12)个较旧答案有何不同。特别是,仅当List/的长度为Tuple已知常数时,您的答案才适用。
jwvh,

谢谢@ jwvh,将考虑您的意见。
Michael Olafisoye

2

2015年。为了使汤姆·克罗基特的答案 更加明确,这是一个真实的例子。

起初,我对此感到困惑。因为我来自Python,所以您可以在这里做tuple(list(1,2,3))
是否缺少Scala语言?(答案是-与Scala或Python无关,与静态类型和动态类型有关。)

这就是导致我试图找到症结所在为何Scala无法做到这一点的原因。


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

toTuple方法在运行时获取类型长度信息,即在编译时没有类型长度信息,因此返回类型Product非常类似于Python的tuple实际类型(每个位置没有类型,并且没有类型长度)。
这种方式容易出现运行时错误,例如类型不匹配或IndexOutOfBoundException。(因此,Python方便的从列表到元组不是免费的午餐。)

相反,使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!

看起来不错-立即尝试
javadba

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

1

根据您的类型:

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

def doSomething(a:Int *)

doSomething(x:_*)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.