Convertir une liste Scala en tuple?

88

Comment puis-je convertir une liste avec (disons) 3 éléments en un tuple de taille 3?

Par exemple, disons que j'ai val x = List(1, 2, 3)et je veux convertir cela en (1, 2, 3). Comment puis-je faire ceci?

grautur
la source
1
Ce n'est pas possible (sauf "à la main") AFAIK. Étant donné def toTuple(x: List[Int]): R, quel devrait être le type de R?
7
Si cela n'a pas besoin d'être fait pour un tuple de taille arbitraire (c'est-à-dire comme une méthode hypothétique présentée ci-dessus), considérez x match { case a :: b :: c :: Nil => (a, b, c); case _ => (0, 0, 0) }et notez que le type résultant est fixé àTuple3[Int,Int,Int]
2
Bien que ce que vous cherchez à faire n'est pas possible avec List, vous pouvez regarder dans le HListtype Shapeless ' , qui permet la conversion vers et depuis les tuples ( github.com/milessabin/… ). Peut-être que cela s'applique à votre cas d'utilisation.

Réponses:

59

Vous ne pouvez pas faire cela de manière sécurisée. Pourquoi? Parce qu'en général, nous ne pouvons pas connaître la longueur d'une liste avant l'exécution. Mais la "longueur" d'un tuple doit être encodée dans son type, et donc connue au moment de la compilation. Par exemple, (1,'a',true)a le type (Int, Char, Boolean), qui est le sucre Tuple3[Int, Char, Boolean]. La raison pour laquelle les tuples ont cette restriction est qu'ils doivent pouvoir gérer des types non homogènes.

Tom Crockett
la source
Existe-t-il une liste d'éléments de taille fixe avec le même type? Ne pas importer de bibliothèques non standard, comme informes, bien sûr.
1
@davips pas à ma connaissance, mais c'est assez facile de créer le vôtre, par exemplecase class Vec3[A](_1: A, _2: A, _3: A)
Tom Crockett
Ce serait un "tuple" d'éléments du même type, je ne peux pas y appliquer de carte, par exemple.
1
@davips comme avec tout nouveau type de données, vous devrez définir comment mapetc fonctionne pour cela
Tom Crockett
1
Ce genre de limitation semble être un indicateur retentissant de l'inutilité de la sécurité de type plus que toute autre chose. Cela me rappelle la citation «l'ensemble vide a de nombreuses propriétés intéressantes».
ely
58

Vous pouvez le faire en utilisant des extracteurs scala et des correspondances de modèles ( lien ):

val x = List(1, 2, 3)

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

Qui renvoie un tuple

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

En outre, vous pouvez utiliser un opérateur générique si vous n'êtes pas sûr de la taille de la liste

val t = x match {
  case List(a, b, c, _*) => (a, b, c)
}
cyrillk
la source
4
Dans le contexte de cette solution, les plaintes concernant la sécurité de type signifient que vous pourriez obtenir une MatchError au moment de l'exécution. Si vous le souhaitez, vous pouvez essayer de détecter cette erreur et faire quelque chose si la liste a la mauvaise longueur. Une autre fonctionnalité intéressante de cette solution est que la sortie ne doit pas nécessairement être un tuple, cela peut être l'une de vos propres classes personnalisées ou une transformation des nombres. Je ne pense pas que vous puissiez le faire avec des non-Lists, cependant (vous devrez peut-être faire une opération .toList à l'entrée).
Jim Pivarski du
Vous pouvez le faire avec n'importe quel type de séquence Scala en utilisant la syntaxe +:. Aussi, n'oubliez pas d'ajouter un fourre-tout
ig-dev
48

un exemple utilisant informe :

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

Remarque: le compilateur a besoin d'informations de type pour convertir la liste dans la liste HList que la raison pour laquelle vous devez passer des informations de type à la toHListméthode

Evantill
la source
18

Shapeless 2.0 a changé une partie de la syntaxe. Voici la solution mise à jour utilisant informe.

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

Le principal problème étant que le type de .toHList doit être spécifié à l'avance. Plus généralement, comme les tuples sont limités dans leur arité, la conception de votre logiciel peut être mieux servie par une solution différente.

Néanmoins, si vous créez une liste de manière statique, envisagez une solution comme celle-ci, utilisant également l'informe. Ici, nous créons directement une HList et le type est disponible au moment de la compilation. N'oubliez pas qu'une HList a des fonctionnalités des types List et Tuple. c'est-à-dire qu'il peut avoir des éléments avec différents types comme un Tuple et peut être mappé parmi d'autres opérations comme des collections standard. Les HLists prennent un peu de temps pour s'y habituer, alors avancez lentement si vous êtes nouveau.

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)
virtualirfan
la source
13

Malgré la simplicité et n'étant pas pour les listes de toute longueur, il est de type sécurisé et la réponse dans la plupart des cas:

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

Autre possibilité, quand vous ne voulez pas nommer la liste ni la répéter (j'espère que quelqu'un pourra montrer un moyen d'éviter les parties 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
cs95
la source
10

FWIW, je voulais un tuple pour initialiser un certain nombre de champs et je voulais utiliser le sucre syntaxique de l'affectation de tuple. PAR EXEMPLE:

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

Il s'avère qu'il existe également du sucre syntaxique pour attribuer le contenu d'une liste ...

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

Donc pas besoin de tuples si vous avez le même problème.

Peter L
la source
@javadba Je cherchais comment implémenter listToTuple () mais j'ai fini par ne pas en avoir besoin. La liste de sucre syntaxique a résolu mon problème.
Peter L du
Il y a aussi une autre syntaxe, qui à mon avis semble un peu meilleure:val List(c1, c2, c3) = myList
Kolmar
7

Vous ne pouvez pas faire cela de manière sécurisée. Dans Scala, les listes sont des séquences de longueur arbitraire d'éléments d'un certain type. Pour autant que le système de typage le sache,x pourrait être une liste de longueur arbitraire.

En revanche, l'arité d'un tuple doit être connue au moment de la compilation. Cela violerait les garanties de sécurité du système de type pour permettre l'attributionx à un type de tuple.

En fait, pour des raisons techniques, les tuples Scala étaient limités à 22 éléments , mais la limite n'existe plus en 2.11 La limite de classe de cas a été levée en 2.11 https://github.com/scala/scala/pull/2305

Il serait possible de coder manuellement une fonction qui convertit des listes de jusqu'à 22 éléments et lève une exception pour les listes plus grandes. La prise en charge des modèles de Scala, une fonctionnalité à venir, rendrait cela plus concis. Mais ce serait un horrible hack.

Escargot mécanique
la source
Le type résultant de cette fonction hypothétique serait-il Any?
La limite de classe de cas a été levée dans 2.11 github.com/scala/scala/pull/2305
gdoubleod
5

Si vous êtes sûr que votre list.size <23 utilisez-le:

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)
Spark-Débutant
la source
5

Cela peut également être fait shapelessavec moins de passe-partout en utilisant 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))

Il est de type sécurisé: vous obtenez None, si la taille du tuple est incorrecte, mais la taille du tuple doit être un littéral ou final val(pour être convertible en shapeless.Nat).

Kolmar
la source
Cela ne semble pas fonctionner pour les tailles supérieures à 22, du moins pour les
shapeless_2.11
@kfkhalili Oui, et ce n'est pas une limitation informe. Scala 2 lui-même n'autorise pas les tuples avec plus de 22 éléments, il n'est donc pas possible de convertir une liste de plus grande taille en un tuple en utilisant n'importe quelle méthode.
Kolmar
4

Utilisation de la correspondance de motifs:

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

Michael Olafisoye
la source
Lorsque vous répondez à une question aussi ancienne (plus de 6 ans), c'est souvent une bonne idée de souligner en quoi votre réponse est différente de toutes les (12) réponses plus anciennes déjà soumises. En particulier, votre réponse n'est applicable que si la longueur du List/ Tupleest une constante connue.
jwvh
Merci @ jwvh, examinera vos commentaires à l'avenir.
Michael Olafisoye
2

Après 2015. Pour que la réponse de Tom Crockett soit plus claire , voici un exemple concret.

Au début, j'étais confus à ce sujet. Parce que je viens de Python, où vous pouvez simplement le faire tuple(list(1,2,3)).
Est-ce à court de langage Scala? (la réponse est - il ne s'agit pas de Scala ou de Python, mais de type statique et de type dynamique.)

Cela me pousse à essayer de trouver la raison pour laquelle Scala ne peut pas faire cela.


L'exemple de code suivant implémente une toTupleméthode, qui a type-safe toTupleNet type-unsafe toTuple.

La toTupleméthode obtient les informations de longueur de type au moment de l'exécution, c'est-à-dire aucune information de longueur de type au moment de la compilation, donc le type de retour Productest très similaire à celui de Python tuple(pas de type à chaque position, et pas de longueur de types).
Cette façon est prononcée à une erreur d'exécution comme type-mismatch ou IndexOutOfBoundException. (Ainsi, la liste à tuple pratique de Python n'est pas un déjeuner gratuit.)

Au contraire, ce sont les informations de longueur fournies par l'utilisateur qui rendent la toTupleNcompilation sûre.

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!
WeiChing 林 煒 清
la source
Looks nice - l'essayer maintenant
StephenBoesch
2

vous pouvez le faire soit

  1. via le pattern-matching (ce que vous ne voulez pas) ou
  2. en parcourant la liste et en appliquant chaque élément un par un.

    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)]
    
comonad
la source
1

dans la mesure où vous avez le type:

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

def doSomething(a:Int *)

doSomething(x:_*)
dzs
la source