【问题标题】:Is there way to create tuple from list(without codegeneration)?有没有办法从列表创建元组(没有代码生成)?
【发布时间】:2020-01-09 19:47:26
【问题描述】:

有时需要从小集合创建元组(例如 scalding 框架)。

def toTuple(list:List[Any]):scala.Product = ...

【问题讨论】:

    标签: scala tuples iterable-unpacking


    【解决方案1】:

    你真的不希望你的方法返回Product,因为这是无用的模糊。如果您希望能够将返回的对象用作元组,那么您必须知道它的数量。所以你可以做的是为不同的arities提供一系列toTupleN方法。为方便起见,您可以将这些作为隐式方法添加到 Seq

    这个怎么样:

    class EnrichedWithToTuple[A](elements: Seq[A]) {
      def toTuple2 = elements match { case Seq(a, b) => (a, b) }
      def toTuple3 = elements match { case Seq(a, b, c) => (a, b, c) }
      def toTuple4 = elements match { case Seq(a, b, c, d) => (a, b, c, d) }
      def toTuple5 = elements match { case Seq(a, b, c, d, e) => (a, b, c, d, e) }
    }
    implicit def enrichWithToTuple[A](elements: Seq[A]) = new EnrichedWithToTuple(elements)
    

    并像这样使用它:

    scala> List(1,2,3).toTuple3
    res0: (Int, Int, Int) = (1,2,3)
    

    【讨论】:

    • 或者,如果您期望单个元组的元组,您可以写出exampleList match { case Seq(a, b, c) => (a, b, c)}
    【解决方案2】:

    如果正如@dhg 所观察到的,您预先知道预期的数量,您可以在这里做一些有用的事情。使用shapeless 你可以写,

    scala> import shapeless._
    import shapeless._
    
    scala> import Traversables._
    import Traversables._
    
    scala> import Tuples._
    import Tuples._
    
    scala> List(1, 2, 3).toHList[Int :: Int :: Int :: HNil] map tupled
    res0: Option[(Int, Int, Int)] = Some((1,2,3))
    

    【讨论】:

    • 但是Shapeless 没有提供从List 创建Tuple 的方法,如果在编译时未定义列表长度,则可能导致运行时错误
    【解决方案3】:

    如果您事先不了解 arity 并想做一个可怕的骇客,您可以这样做:

    def toTuple[A <: Object](as:List[A]):Product = {
      val tupleClass = Class.forName("scala.Tuple" + as.size)
      tupleClass.getConstructors.apply(0).newInstance(as:_*).asInstanceOf[Product]
    }
    toTuple: [A <: java.lang.Object](as: List[A])Product
    
    scala> toTuple(List("hello", "world"))
    res15: Product = (hello,world)
    

    【讨论】:

    • +1 对于从字符串中清理分配非常有用。它不适用于 var (a, b, c) = toTuple(myIter.toList) - 有什么想法吗?
    • Rubistro:为此您可以使用var List(a,b,c) = myIter.toList
    【解决方案4】:

    您想要Tuple 还是只需要Product。因为对于后者:

    case class SeqProduct[A](elems: A*) {
      override def productArity: Int = elems.size
      override def productElement(i: Int) = elems(i)
    }
    
    SeqProduct(List(1, 2, 3): _*)
    

    【讨论】:

    • 也希望Tuple,所以我可以使用FunctionX.tupled,这确实需要TupleX,而不是Product(注意甚至ProductX)。
    【解决方案5】:

    基于@Kim Stebel 的想法,我编写了一个从 seq 创建元组的简单实用程序。

    import java.lang.reflect.Constructor
    
    /**
     * Created by Bowen Cai on 1/24/2015.
     */
    sealed trait Product0 extends Any with Product {
    
      def productArity = 0
      def productElement(n: Int) = throw new IllegalStateException("No element")
      def canEqual(that: Any) = false
    }
    object Tuple0 extends Product0 {
      override def toString() = "()"
    }
    
    case class SeqProduct(elems: Any*) extends Product {
      override def productArity: Int = elems.size
      override def productElement(i: Int) = elems(i)
      override def toString() = elems.addString(new StringBuilder(elems.size * 8 + 10), "(" , ",", ")").toString()
    }
    
    object Tuples {
    
      private[this] val ctors = {
        val ab = Array.newBuilder[Constructor[_]]
        for (i <- 1 to 22) {
          val tupleClass = Class.forName("scala.Tuple" + i)
          ab += tupleClass.getConstructors.apply(0)
        }
        ab.result()
      }
    
      def toTuple(elems: Seq[AnyRef]): Product = elems.length match {
        case 0 => Tuple0
        case size if size <= 22 =>
          ctors(size - 1).newInstance(elems: _*).asInstanceOf[Product]
        case size if size > 22 => new SeqProduct(elems: _*)
      }
    
    }
    

    【讨论】:

    • 谢谢你,@xKommando。这正是我所需要的。因为我希望 toTuple 应用于 Any 而不是 AnyRef 的序列,所以我用 Seq[Any] 替换了 elems 的类型 并用val refs = for (e &lt;- elems) yield e.asInstanceOf[AnyRef] ctors(size - 1).newInstance(refs: _*).asInstanceOf[Product] 替换案例size&lt;=22 的表达式(基于此处的答案:[link] (stackoverflow.com/questions/16751484/…))
    【解决方案6】:
    scala> val numbers = Seq(1,2,4)
    numbers: Seq[Int] = List(1, 2, 4)
    
    scala> val string = numbers.mkString("(",",",")")
    string: String = (1,2,4)
    
    *** mkString(start:String, sep: String, end: String)
    

    我在 where-in-clause 中生成了它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-09-07
      • 2021-04-13
      • 1970-01-01
      • 2023-02-04
      • 1970-01-01
      • 2015-08-19
      • 2021-10-25
      • 2019-04-24
      相关资源
      最近更新 更多