Eine Scala-Liste in ein Tupel umwandeln?

Wie kann ich eine Liste mit (sagen wir) 3 Elementen in ein Tupel der Größe 3 konvertieren?

Nehmen wir zum Beispiel an, ich habe val x = List(1, 2, 3) und ich möchte das in (1, 2, 3) umwandeln. Wie kann ich das machen?

Dies kann nicht typsicher gemacht werden. Warum? Weil wir die Länge einer Liste im Allgemeinen nicht bis zur Laufzeit kennen können. Aber die “Länge” eines Tupels muss in seinem Typ codiert sein und daher zur Kompilierungszeit bekannt sein. Zum Beispiel hat (1,'a',true) den Typ (Int, Char, Boolean) , der Zucker für Tuple3[Int, Char, Boolean] . Der Grund dafür, dass Tupel diese Einschränkung haben, ist, dass sie in der Lage sein müssen, mit inhomogenen Typen umzugehen.

Sie können es mit Hilfe von Scala-Extraktoren und Pattern-Matching ( Link ) tun:

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

Welches gibt ein Tupel zurück

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

Sie können auch einen Platzhalter verwenden, wenn Sie sich über die Größe der Liste nicht sicher sind

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

ein Beispiel, das formlos verwendet :

 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 

Hinweis: Der Compiler benötigt einige Informationen, um die Liste im HList zu konvertieren. Dies ist der Grund, warum Sie toHList an die Methode toHList

Shapeless 2.0 hat einige Syntax geändert. Hier ist die aktualisierte Lösung, die formlos verwendet.

 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 

Das Hauptproblem ist, dass der Typ für .toHList im Voraus angegeben werden muss. Da Tupel in ihrer Komplexität begrenzt sind, kann das Design Ihrer Software besser durch eine andere Lösung bedient werden.

Wenn Sie jedoch eine Liste statisch erstellen, sollten Sie eine Lösung wie diese in Betracht ziehen, die ebenfalls formlos ist. Hier erstellen wir direkt einen HList und der Typ ist zur Kompilierzeit verfügbar. Denken Sie daran, dass ein HList functionen sowohl von List- als auch von Tuple-Typen hat. Dh es kann Elemente mit verschiedenen Typen wie ein Tupel haben und kann unter anderen Operationen wie Standard-Sammlungen abgebildet werden. HLists brauchen eine Weile, um sich daran zu gewöhnen, aber gehen Sie langsam vor, wenn Sie neu sind.

 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) 

FWIW, ich wollte ein Tupel, um eine Reihe von Feldern zu initialisieren, und wollte den syntaktischen Zucker der Tupel-Zuweisung verwenden. Z.B:

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

Es stellt sich heraus, dass es auch syntaktischen Zucker gibt, um den Inhalt einer Liste zuzuordnen …

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

Also keine Notwendigkeit für Tupel, wenn Sie das gleiche Problem haben.

Trotz der Einfachheit und nicht für Listen von beliebiger Länge ist es typsicher und die Antwort in den meisten Fällen:

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

Eine andere Möglichkeit, wenn du die Liste weder benennen noch wiederholen willst (ich hoffe jemand kann einen Weg zeigen, die Seq / Kopfteile zu vermeiden):

 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 

Bearbeiten: fehlende Klammer hinzugefügt

Sie können dies nicht typsicher machen. In Scala sind Listen willkürliche Sequenzen von Elementen eines bestimmten Typs. Soweit das Typsystem weiß, könnte x eine Liste beliebiger Länge sein.

Im Gegensatz dazu muss die Komplexität eines Tupels zur Kompilierzeit bekannt sein. Es würde die Sicherheitsgarantien des Typsystems verletzen, um x einem Tupeltyp zuzuordnen.

Tatsächlich waren Scala-Tupel aus technischen Gründen auf 22 Elemente beschränkt , aber das Limit existiert nicht mehr in 2.11. Das classnlimit wurde in 2.11 https://github.com/scala/scala/pull/2305 aufgehoben

Es wäre möglich, eine function manuell zu codieren, die Listen von bis zu 22 Elementen konvertiert und eine Ausnahme für größere Listen austriggers. Scala’s Template-Unterstützung, eine kommende function, würde dies prägnanter machen. Aber das wäre ein hässlicher Hack.

soweit du den Typ hast:

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

Dies kann auch in shapeless mit weniger Standardgröße mit 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)) 

Es ist typsicher: Sie erhalten None , wenn die Tupelgröße nicht korrekt ist, aber die Tupelgröße muss ein Literal oder final val (um in shapeless.Nat konvertiert zu werden).

Sie können dies auch tun

  1. über Mustervergleich (was Sie nicht wollen) oder
  2. indem Sie die Liste durchlaufen und jedes Element einzeln anwenden.

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

Wenn du dir sicher bist, dass deine list.size <23 es benutzt:

 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) 

2015 Post. Um die Erklärung von Tom Crockett genauer zu formulieren , hier ein echtes Beispiel.

Anfangs war ich verwirrt. Weil ich von Python komme, wo man einfach tuple(list(1,2,3)) .
Ist es nicht die Sprache von Scala? (Die Antwort ist – es geht nicht um Scala oder Python, es geht um statischen Typ und dynamischen Typ.)

Das ist der Grund, warum ich versuche, den Grund zu finden, warum Scala das nicht kann.


Im folgenden Codebeispiel wird eine toTuple Methode implementiert, die type-safe toTupleN und type-unsafe toTuple .

Die toTuple Methode erhält zur toTuple die type-length-Informationen, dh zum Zeitpunkt der Kompilierung keine type-length-Informationen. Der Rückgabetyp ist also Product der dem tuple des Python sehr ähnlich ist (kein Typ an jeder Position und keine Länge von Arten).
Auf diese Weise wird ein Laufzeiterrors wie type-mismatch oder IndexOutOfBoundException . (Pythons praktisches Listen-zu-Tupel ist kein kostenloses Mittagessen.)

Im Gegensatz dazu ist es die vom Benutzer bereitgestellte toTupleN , die toTupleN Kompilierungszeit sicher macht.

 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!