Eine Scala-Liste in ein Tupel umwandeln?

86

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 dies in konvertieren (1, 2, 3). Wie kann ich das machen?

grautur
quelle
1
Es ist nicht möglich (außer "von Hand") AFAIK. In Anbetracht def toTuple(x: List[Int]): R, was sollte die Art der R?
7
Wenn dies für ein Tupel beliebiger Größe nicht erforderlich ist (dh wie bei einer oben dargestellten hypothetischen Methode), berücksichtigen Sie x match { case a :: b :: c :: Nil => (a, b, c); case _ => (0, 0, 0) }und beachten Sie, dass der resultierende Typ aufTuple3[Int,Int,Int]
2
Während das, was Sie tun Listmöchten, nicht möglich ist , können Sie sich den HListTyp Shapeless ansehen , der die Konvertierung in und von Tupeln ermöglicht ( github.com/milessabin/… ). Vielleicht ist es für Ihren Anwendungsfall anwendbar.

Antworten:

57

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

Tom Crockett
quelle
Gibt es eine Liste fester Größe von Elementen mit demselben Typ? Natürlich keine nicht standardmäßigen Bibliotheken wie formlose importieren.
1
@davips nicht, dass ich weiß, aber es ist einfach genug, Ihre eigenen zu machen, zBcase class Vec3[A](_1: A, _2: A, _3: A)
Tom Crockett
Dies wäre ein "Tupel" von Elementen desselben Typs. Ich kann beispielsweise keine Karte darauf anwenden.
1
@davips wie bei jedem neuen Datentyp müssten Sie definieren, wie mapetc dafür funktioniert
Tom Crockett
1
Diese Art der Einschränkung scheint vor allem ein eindeutiger Indikator für die Nutzlosigkeit der Typensicherheit zu sein. Es erinnert mich an das Zitat "Die leere Menge hat viele interessante Eigenschaften."
Ely
57

Sie können dies mit 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 Platzhalteroperator 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)
}
Cyrillk
quelle
4
Im Zusammenhang mit dieser Lösung führen Beschwerden über die Typensicherheit dazu, dass Sie zur Laufzeit möglicherweise einen MatchError erhalten. Wenn Sie möchten, können Sie versuchen, diesen Fehler abzufangen und etwas zu tun, wenn die Liste die falsche Länge hat. Ein weiteres nettes Merkmal dieser Lösung ist, dass die Ausgabe kein Tupel sein muss, sondern eine Ihrer eigenen benutzerdefinierten Klassen oder eine Transformation der Zahlen sein kann. Ich glaube jedoch nicht, dass Sie dies mit Nicht-Listen tun können (daher müssen Sie möglicherweise eine .toList-Operation für die Eingabe ausführen).
Jim Pivarski
Sie können dies mit jeder Art von Scala-Sequenz mit der Syntax +: tun. Vergessen Sie auch nicht, ein Allheilmittel hinzuzufügen
ig-dev
48

ein Beispiel mit formlos :

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 Typinformationen, um die Liste in der HList zu konvertieren. Dies ist der Grund, warum Sie Typinformationen an die toHListMethode übergeben müssen

evantill
quelle
18

Shapeless 2.0 hat die Syntax geändert. Hier ist die aktualisierte Lösung mit 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

Das Hauptproblem besteht darin, dass der Typ für .toHList im Voraus angegeben werden muss. Da Tupel in ihrer Arität begrenzt sind, kann das Design Ihrer Software im Allgemeinen besser von einer anderen Lösung unterstützt werden.

Wenn Sie jedoch eine Liste statisch erstellen, sollten Sie eine Lösung wie diese in Betracht ziehen, die auch formlos ist. Hier erstellen wir direkt eine HList und der Typ ist zur Kompilierungszeit verfügbar. Denken Sie daran, dass eine HList Funktionen sowohl vom Listen- als auch vom Tupeltyp enthält. Das heißt, es kann Elemente mit unterschiedlichen Typen wie ein Tupel haben und kann unter anderen Operationen wie Standardsammlungen zugeordnet werden. HListen brauchen eine Weile, um sich daran zu gewöhnen, also treten Sie langsam, 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)
virtualirfan
quelle
13

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

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 Sie die Liste nicht benennen oder wiederholen möchten (ich hoffe, jemand kann einen Weg zeigen, um die Seq / head-Teile 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
cs95
quelle
10

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

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

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

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

Sie brauchen also keine Tupel, wenn Sie das gleiche Problem haben.

Peter L.
quelle
@javadba Ich habe nach einer Implementierung von listToTuple () gesucht, musste sie aber nicht. Die Liste syntaktischer Zucker löste mein Problem.
Peter L
Es gibt auch eine andere Syntax, die meiner Meinung nach etwas besser aussieht:val List(c1, c2, c3) = myList
Kolmar
7

Sie können dies nicht typsicher tun. In Scala sind Listen Sequenzen beliebiger Länge von Elementen eines Typs. Soweit das Typsystem weiß, xkönnte es sich um eine Liste beliebiger Länge handeln.

Im Gegensatz dazu muss die Arität eines Tupels zur Kompilierungszeit bekannt sein. Es würde die Sicherheitsgarantien des Typsystems verletzen, die Zuordnung xzu einem Tupeltyp zu ermöglichen .

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

Es wäre möglich, eine Funktion manuell zu codieren, die Listen mit bis zu 22 Elementen konvertiert und eine Ausnahme für größere Listen auslöst. Die Vorlagenunterstützung von Scala, eine bevorstehende Funktion, würde dies präziser machen. Aber das wäre ein hässlicher Hack.

Mechanische Schnecke
quelle
Wäre der resultierende Typ dieser hypothetischen Funktion Any?
Das
Fallklassenlimit wurde
5

Dies kann auch shapelessmit weniger Boilerplate erfolgen, indem 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 falsch ist, aber die Tupelgröße muss ein Literal sein oder final val(um konvertierbar zu sein shapeless.Nat).

Kolmar
quelle
Dies scheint nicht für Größen größer als 22 zu funktionieren, zumindest für
shapeeless_2.11
@kfkhalili Ja, und es ist keine formlose Einschränkung. Scala 2 selbst erlaubt keine Tupel mit mehr als 22 Elementen, daher ist es mit keiner Methode möglich, eine Liste größerer Größe in ein Tupel zu konvertieren.
Kolmar
4

Wenn Sie sehr sicher sind, dass Ihre list.size <23 verwendet wird:

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-Anfänger
quelle
4

Verwenden der Musteranpassung:

val intTuple = Liste (1,2,3) entspricht {Fallliste (a, b, c) => (a, b, c)}

Michael Olafisoye
quelle
Wenn Sie eine so alte Frage beantworten (über 6 Jahre), ist es oft eine gute Idee, darauf hinzuweisen, dass sich Ihre Antwort von allen (12) älteren Antworten unterscheidet, die bereits eingereicht wurden. Insbesondere gilt Ihre Antwort nur, wenn die Länge des List/ Tupleeine bekannte Konstante ist.
Jwvh
Vielen Dank @ jwvh, ich werde Ihre Kommentare in Zukunft berücksichtigen.
Michael Olafisoye
2

Beitrag 2015. Damit die Antwort von Tom Crockett klarer wird , hier ein echtes Beispiel.

Zuerst war ich verwirrt. Weil ich aus Python komme, wo du es einfach machen kannst tuple(list(1,2,3)).
Fehlt es an Scala-Sprache? (Die Antwort lautet: Es geht nicht um Scala oder Python, es geht um statischen und dynamischen Typ.)

Das bringt mich dazu, den Kern zu finden, warum Scala das nicht kann.


Das folgende Codebeispiel implementiert eine toTupleMethode, die typsicher toTupleNund typsicher ist toTuple.

Die toTupleMethode ruft die Informationen zur Typlänge zur Laufzeit ab, dh keine Informationen zur Typlänge zur Kompilierungszeit, sodass der Rückgabetyp Productdem des Pythons sehr ähnlich ist tuple(kein Typ an jeder Position und keine Länge der Typen).
Auf diese Weise kommt es zu Laufzeitfehlern wie Typinkongruenz oder IndexOutOfBoundException. (Pythons praktisches List-to-Tupel ist also kein kostenloses Mittagessen.)

Im Gegensatz dazu ist es die vom Benutzer bereitgestellte Längeninformation, die die toTupleNKompilierungszeit 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!
WeiChing 林 煒 清
quelle
Sieht gut aus - probiere es jetzt aus
javadba
2

Sie können dies auch tun

  1. über Pattern-Matching (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)]
Comonad
quelle
1

soweit du den typ hast:

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

def doSomething(a:Int *)

doSomething(x:_*)
dzs
quelle