Convert a Scala list to a tuple?
Asked Answered
R

14

97

How can I convert a list with (say) 3 elements into a tuple of size 3?

For example, let's say I have val x = List(1, 2, 3) and I want to convert this into (1, 2, 3). How can I do this?

Reiche answered 6/2, 2013 at 6:23 Comment(3)
It's not possible (except "by hand") AFAIK. Given def toTuple(x: List[Int]): R, what should the type of R be?Kentigera
If it doesn't need to be done for an arbitrary-sized tuple (i.e. like a hypothetical method presented above), consider x match { case a :: b :: c :: Nil => (a, b, c); case _ => (0, 0, 0) } and note the resulting type is fixed at Tuple3[Int,Int,Int]Kentigera
While what you seek to do is not possible with List, you could look into Shapeless' HList type, which allows conversion to and from tuples (github.com/milessabin/…). Maybe it's applicable for your use case.Reiser
H
62

You can't do this in a typesafe way. Why? Because in general we can't know the length of a list until runtime. But the "length" of a tuple must be encoded in its type, and hence known at compile time. For example, (1,'a',true) has the type (Int, Char, Boolean), which is sugar for Tuple3[Int, Char, Boolean]. The reason tuples have this restriction is that they need to be able to handle a non-homogeneous types.

Heritable answered 6/2, 2013 at 6:31 Comment(5)
Is there some fixed-size list of elements with the same type? Not importing nonstandard libraries, like shapeless, of course.Matusow
@davips not that I know of, but it's easy enough to make your own, e.g. case class Vec3[A](_1: A, _2: A, _3: A)Heritable
This would be a "tuple" of elements with the same type, I can't apply map on it, for instance.Matusow
@davips as with any new data type you'd have to define how map etc works for itHeritable
This kind of limitation seems to be a resounding indicator of the uselessness of type safety more than anything else. It reminds me of the quote "the empty set has many interesting properties."Foskett
L
62

You can do it using scala extractors and pattern matching (link):

val x = List(1, 2, 3)

val t = x match {
  case List(a, b, c) => (a, b, c)
}

Which returns a tuple

t: (Int, Int, Int) = (1,2,3)

Also, you can use a wildcard operator if not sure about a size of the List

val t = x match {
  case List(a, b, c, _*) => (a, b, c)
}
Lurette answered 14/2, 2014 at 0:9 Comment(2)
In the context of this solution, the complaints about type-safety mean that you might get a MatchError at runtime. If you want, you can try-catch that error and do something if the List has the wrong length. Another nice feature of this solution is that the output doesn't have to be a tuple, it can be one of your own custom classes or some transformation of the numbers. I don't think you can do this with non-Lists, though (so you may need to do a .toList operation to the input).Rodrich
You can do this with any type of Scala sequence using the +: syntax. Also, don't forget to add a catch-allUpstart
A
49

an example using 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

Note: the compiler need some type informations to convert the List in the HList that the reason why you need to pass type informations to the toHList method

Anoa answered 6/2, 2013 at 11:29 Comment(0)
C
18

Shapeless 2.0 changed some syntax. Here's the updated solution using 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

The main issue being that the type for .toHList has to be specified ahead of time. More generally, since tuples are limited in their arity, the design of your software might be better served by a different solution.

Still, if you are creating a list statically, consider a solution like this one, also using shapeless. Here, we create an HList directly and the type is available at compile time. Remember that an HList has features from both List and Tuple types. i.e. it can have elements with different types like a Tuple and can be mapped over among other operations like standard collections. HLists take a little while to get used to though so tread slowly if you are new.

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)
Canaday answered 11/11, 2013 at 7:51 Comment(0)
M
15

Despite the simplicity and being not for lists of any length, it is type-safe and the answer in most cases:

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

Another possibility, when you don't want to name the list nor to repeat it (I hope someone can show a way to avoid the Seq/head parts):

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
Matusow answered 25/8, 2013 at 5:30 Comment(0)
M
14

FWIW, I wanted a tuple to initalise a number of fields and wanted to use the syntactic sugar of tuple assignment. EG:

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

It turns out that there is syntactic sugar for assigning the contents of a list too...

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

So no need for tuples if you've got the same problem.

Mercedes answered 29/1, 2015 at 3:38 Comment(2)
@javadba I was looking for how to implement listToTuple() but ended up not needing to. The list syntactic sugar solved my problem.Mercedes
There is also another syntax, which in my opinion looks a bit better: val List(c1, c2, c3) = myListBoggs
U
9

If you are very sure that your list.size<23 use it:

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)
Undesirable answered 31/5, 2017 at 17:44 Comment(0)
R
8

You can't do this in a type-safe way. In Scala, lists are arbitrary-length sequences of elements of some type. As far as the type system knows, x could be a list of arbitrary length.

In contrast, the arity of a tuple must be known at compile time. It would violate the safety guarantees of the type system to allow assigning x to a tuple type.

In fact, for technical reasons, Scala tuples were limited to 22 elements, but the limit no longer exists in 2.11 The case class limit has been lifted in 2.11 https://github.com/scala/scala/pull/2305

It would be possible to manually code a function that converts lists of up to 22 elements, and throws an exception for larger lists. Scala's template support, an upcoming feature, would make this more concise. But this would be an ugly hack.

Repairer answered 6/2, 2013 at 6:31 Comment(2)
Would the resulting type of this hypothetical function be Any?Matusow
The case class limit has been lifted in 2.11 github.com/scala/scala/pull/2305Vassal
B
6

This can also be done in shapeless with less boilerplate using 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))

It's type-safe: you get None, if the tuple size is incorrect, but the tuple size must be a literal or final val (to be convertible to shapeless.Nat).

Boggs answered 22/6, 2017 at 18:13 Comment(2)
This does not appear to work for sizes larger than 22, at least for shapeless_2.11:2.3.3Carmen
@Carmen Yes, and it's not a shapeless limitation. Scala 2 itself doesn't allow tuples with more than 22 elements, so it's not possible to convert a List of a larger size to a Tuple using any method.Boggs
P
6

Using Pattern Matching:

val intTuple = List(1,2,3) match {case List(a, b, c) => (a, b, c)}
Pernell answered 6/5, 2019 at 9:33 Comment(2)
When answering a question this old (over 6 years) it's often a good idea to point out how your answer is different from all the (12) older answers already submitted. In particular, your answer is only applicable if the length of the List/Tuple is a known constant.Faitour
Thank you @ jwvh, will consider your comments going forward.Pernell
A
2

2015 post. For the Tom Crockett's answer to be more clarifying, here is a real example.

At first, I got confused about it. Because I come from Python, where you can just do tuple(list(1,2,3)).
Is it short of Scala language ? (the answer is -- it's not about Scala or Python, it's about static-type and dynamic-type.)

That's causes me trying to find the crux why Scala can't do this .


The following code example implements a toTuple method, which has type-safe toTupleN and type-unsafe toTuple.

The toTuple method get the type-length information at run-time, i.e no type-length information at compile-time, so the return type is Product which is very like the Python's tuple indeed (no type at each position, and no length of types).
That way is proned to runtime error like type-mismatch or IndexOutOfBoundException. (so Python's convenient list-to-tuple is not free lunch. )

Contrarily , it is the length information user provided that makes toTupleN compile-time safe.

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!
Aria answered 22/4, 2015 at 5:4 Comment(0)
W
2

you can do this either

  1. via pattern-matching (what you do not want) or
  2. by iterating through the list and applying each element one by one.

    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)]
    
Wesson answered 16/3, 2016 at 19:7 Comment(0)
O
1

In scala 3, you can do something like this:

    def totuple[A](as: List[A]): Tuple = as match
        case Nil => EmptyTuple
        case h :: t => h *: totuple(t)

but as has been said already, without giving the compiler any more hard-coded type information, you aren't going to know the length of the tuple or the types of its elements, so this is likely hardly any better than the original list.

Outcaste answered 16/2, 2023 at 5:58 Comment(0)
P
0

as far as you have the type:

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

def doSomething(a:Int *)

doSomething(x:_*)
Pulmonary answered 15/10, 2013 at 2:12 Comment(1)
only traits and abstract classes can have declared but undefined members. you have to make sure you test your solution before posting it.Suppliant

© 2022 - 2024 — McMap. All rights reserved.