Is there way to create tuple from list(without codegeneration)?
Asked Answered
G

6

16

Sometimes there are needs to create tuples from small collections(for example scalding framework).

def toTuple(list:List[Any]):scala.Product = ...
Gerrilee answered 3/7, 2012 at 6:2 Comment(0)
D
12

If you don't know the arity up front and want to do a terrible terrible hack, you can do this:

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)
Deryl answered 3/7, 2012 at 13:41 Comment(2)
+1 Very useful for cleaning up assignment from strings. It doesn't work for var (a, b, c) = toTuple(myIter.toList) - Any ideas?Patmore
Rubistro: for that purpose you can use var List(a,b,c) = myIter.toListDeryl
A
15

You really don't want your method to return Product since this is uselessly vague. If you want to be able to use the returned object as a tuple, then you'll have to know its arity. So what you can do is have a series of toTupleN methods for different arities. For convenience, you can add these as implicit methods on Seq.

How about this:

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)

and use it like:

scala> List(1,2,3).toTuple3
res0: (Int, Int, Int) = (1,2,3)
Agama answered 3/7, 2012 at 6:5 Comment(1)
Alternately, if you are expecting tuples of a single arity, you can just write out exampleList match { case Seq(a, b, c) => (a, b, c)}Rotator
O
14

If, as @dhg observed, you know the expected arity up front you can do something useful here. Using shapeless you could write,

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))
Ocarina answered 3/7, 2012 at 8:41 Comment(1)
But Shapeless doesn't provide the way to create a Tuple from List if length of list is undefined in compile time, it can lead to runtime errorsRobison
D
12

If you don't know the arity up front and want to do a terrible terrible hack, you can do this:

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)
Deryl answered 3/7, 2012 at 13:41 Comment(2)
+1 Very useful for cleaning up assignment from strings. It doesn't work for var (a, b, c) = toTuple(myIter.toList) - Any ideas?Patmore
Rubistro: for that purpose you can use var List(a,b,c) = myIter.toListDeryl
C
5

Do you want a Tuple or just a Product. Because for the latter:

case class SeqProduct[A](elems: A*) {
  override def productArity: Int = elems.size
  override def productElement(i: Int) = elems(i)
}

SeqProduct(List(1, 2, 3): _*)
Caesium answered 3/7, 2012 at 15:28 Comment(1)
Also wishing for Tuple, so I could use FunctionX.tupled, which indeed requires a TupleX, not Product (note even ProductX).Pung
D
1

Based on @Kim Stebel's idea, I wrote a simple utility that creates tuple from 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: _*)
  }

}
Demogorgon answered 24/1, 2015 at 17:32 Comment(1)
Thank you, @xKommando. This is just what I needed. Because I wanted toTuple to apply to a sequence of Any rather than AnyRef, I replaced the type of elems with Seq[Any] and replaced the expression for the case size<=22 with val refs = for (e <- elems) yield e.asInstanceOf[AnyRef] ctors(size - 1).newInstance(refs: _*).asInstanceOf[Product] (based on the answer here: [link] (#16751984))Pagandom
S
0
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)

I have generated it in my where-in-clause.

Sikorsky answered 9/1, 2020 at 10:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.