【问题标题】:Convert a Scala list to a tuple?将 Scala 列表转换为元组?
【发布时间】:2013-01-21 06:49:54
【问题描述】:

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

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

【问题讨论】:

  • 这是不可能的(除了“手工”)AFAIK。给定def toTuple(x: List[Int]): R,R的类型应该是什么?
  • 如果不需要对任意大小的元组执行此操作(即像上面介绍的假设方法),请考虑x match { case a :: b :: c :: Nil => (a, b, c); case _ => (0, 0, 0) } 并注意生成的类型固定为Tuple3[Int,Int,Int]跨度>
  • 虽然List 无法实现您想要做的事情,但您可以查看Shapeless 的HList 类型,它允许在元组之间进行转换(github.com/milessabin/…)。也许它适用于您的用例。

标签: list scala tuples


【解决方案1】:

您可以使用 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)
}

【讨论】:

  • 在此解决方案的上下文中,关于类型安全的抱怨意味着您可能会在运行时收到 MatchError。如果您愿意,您可以尝试捕获该错误并在列表长度错误时执行某些操作。此解决方案的另一个不错的功能是输出不必是元组,它可以是您自己的自定义类之一或数字的某种转换。不过,我认为您不能对非列表执行此操作(因此您可能需要对输入执行 .toList 操作)。
  • 您可以使用 +: 语法对任何类型的 Scala 序列执行此操作。另外,不要忘记添加一个包罗万象的内容
【解决方案2】:

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

【讨论】:

  • 是否有一些具有相同类型的固定大小的元素列表?当然,不要导入非标准库,比如 shapeless。
  • @davips 不是我所知道的,但制作你自己的很容易,例如case class Vec3[A](_1: A, _2: A, _3: A)
  • 这将是具有相同类型的元素的“元组”,例如,我不能在其上应用地图。
  • @davips 与任何新数据类型一样,您必须定义 map 等如何为其工作
  • 这种限制似乎是类型安全无用的一个响亮的指标。它让我想起了一句话“空集有很多有趣的属性。”
【解决方案3】:

使用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 方法

【讨论】:

    【解决方案4】:

    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)
    

    【讨论】:

      【解决方案5】:

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

      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
      

      【讨论】:

        【解决方案6】:

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

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

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

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

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

        【讨论】:

        • @javadba 我一直在寻找如何实现 listToTuple() 但最终不需要。列表语法糖解决了我的问题。
        • 还有另一种语法,在我看来更好一些:val List(c1, c2, c3) = myList
        【解决方案7】:

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

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

        其实由于技术原因,Scala tuples were limited to 22 elements,但是限制在 2.11 中不再存在 case class 限制在 2.11 中已经解除 https://github.com/scala/scala/pull/2305

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

        【讨论】:

        【解决方案8】:

        如果你非常确定你的 list.size

        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)
        

        【讨论】:

          【解决方案9】:

          使用模式匹配:

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

          【讨论】:

          • 在回答这么久(超过 6 年)的问题时,指出您的答案与已提交的所有 (12) 个旧答案有何不同通常是个好主意。特别是,您的答案仅适用于List/Tuple 的长度是已知常数的情况。
          • 谢谢@jwvh,以后会考虑你的cmets。
          【解决方案10】:

          这也可以在 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:2.3.3
          • @kfkhalili 是的,这不是无形的限制。 Scala 2 本身不允许包含超过 22 个元素的元组,因此无法使用任何方法将更大尺寸的 List 转换为 Tuple。
          【解决方案11】:

          2015 年发布。 为了让Tom Crockett's answer 更加澄清,这里有一个真实的例子。

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

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


          以下代码示例实现了一个toTuple 方法,该方法具有类型安全的toTupleN 和类型不安全的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!
          

          【讨论】:

            【解决方案12】:

            你也可以这样做

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

            【讨论】:

              【解决方案13】:

              只要你有类型:

              val x: List[Int] = List(1, 2, 3)
              
              def doSomething(a:Int *)
              
              doSomething(x:_*)
              

              【讨论】:

              • 只有特征和抽象类可以有声明但未定义的成员。您必须确保在发布解决方案之前对其进行测试。
              猜你喜欢
              • 2015-02-13
              • 2018-08-03
              • 1970-01-01
              • 1970-01-01
              • 2020-04-27
              • 2016-09-28
              • 1970-01-01
              相关资源
              最近更新 更多