Come posso convertire una lista con (diciamo) 3 elementi in una tupla di dimensione 3?
Ad esempio, diciamo che ho val x = List(1, 2, 3)
e voglio convertirlo in (1, 2, 3)
. Come posso fare questo?
Come posso convertire una lista con (diciamo) 3 elementi in una tupla di dimensione 3?
Ad esempio, diciamo che ho val x = List(1, 2, 3)
e voglio convertirlo in (1, 2, 3)
. Come posso fare questo?
x match { case a :: b :: c :: Nil => (a, b, c); case _ => (0, 0, 0) }
e nota che il tipo risultante è fissato aTuple3[Int,Int,Int]
List
, potresti esaminare il HList
tipo di Shapeless , che consente la conversione da e verso le tuple ( github.com/milessabin/… ). Forse è applicabile al tuo caso d'uso.
Risposte:
Non puoi farlo in modo sicuro. Perché? Perché in generale non possiamo conoscere la lunghezza di un elenco fino al runtime. Ma la "lunghezza" di una tupla deve essere codificata nel suo tipo, e quindi nota in fase di compilazione. Ad esempio, (1,'a',true)
ha il tipo (Int, Char, Boolean)
, che è lo zucchero per Tuple3[Int, Char, Boolean]
. Il motivo per cui le tuple hanno questa restrizione è che devono essere in grado di gestire tipi non omogenei.
case class Vec3[A](_1: A, _2: A, _3: A)
map
funziona ecc.
Puoi farlo usando gli estrattori scala e il pattern matching ( link ):
val x = List(1, 2, 3)
val t = x match {
case List(a, b, c) => (a, b, c)
}
Che restituisce una tupla
t: (Int, Int, Int) = (1,2,3)
Inoltre, puoi utilizzare un operatore jolly se non sei sicuro della dimensione dell'elenco
val t = x match {
case List(a, b, c, _*) => (a, b, c)
}
un esempio usando 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
Nota: il compilatore necessita di alcune informazioni sul tipo per convertire la lista nella HList che è il motivo per cui è necessario passare le informazioni sul tipo al toHList
metodo
Shapeless 2.0 ha cambiato alcune sintassi. Ecco la soluzione aggiornata utilizzando 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
Il problema principale è che il tipo di .toHList deve essere specificato in anticipo. Più in generale, poiché le tuple sono limitate nella loro arità, la progettazione del software potrebbe essere meglio servita da una soluzione diversa.
Tuttavia, se stai creando una lista staticamente, considera una soluzione come questa, usando anche informe. Qui creiamo direttamente una HList e il tipo è disponibile in fase di compilazione. Ricorda che una HList ha funzionalità sia dei tipi List che di Tuple. cioè può avere elementi con tipi diversi come una tupla e può essere mappato tra le altre operazioni come le raccolte standard. HList richiede un po 'di tempo per abituarsi, quindi procedi lentamente se sei nuovo.
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)
Nonostante la semplicità e il fatto che non sia per elenchi di qualsiasi lunghezza, è indipendente dai tipi e nella maggior parte dei casi la risposta:
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))
Un'altra possibilità, quando non vuoi nominare la lista né ripeterla (spero che qualcuno possa mostrare un modo per evitare le parti 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
FWIW, volevo una tupla per inizializzare un numero di campi e volevo usare lo zucchero sintattico dell'assegnazione di tupla. PER ESEMPIO:
val (c1, c2, c3) = listToTuple(myList)
Si scopre che c'è anche zucchero sintattico per assegnare il contenuto di una lista ...
val c1 :: c2 :: c3 :: Nil = myList
Quindi non c'è bisogno di tuple se hai lo stesso problema.
val List(c1, c2, c3) = myList
Non puoi farlo in modo indipendente dai tipi. In Scala, le liste sono sequenze di lunghezza arbitraria di elementi di qualche tipo. Per quanto ne sa il sistema dei tipi,x
potrebbe essere un elenco di lunghezza arbitraria.
Al contrario, l'arità di una tupla deve essere nota in fase di compilazione. Violare le garanzie di sicurezza del sistema di tipo per consentire l'assegnazionex
a un tipo di tupla.
Infatti, per ragioni tecniche, le tuple Scala erano limitate a 22 elementi , ma il limite non esiste più in 2.11 Il limite di classe case è stato rimosso nella 2.11 https://github.com/scala/scala/pull/2305
Sarebbe possibile codificare manualmente una funzione che converte elenchi fino a 22 elementi e genera un'eccezione per elenchi più grandi. Il supporto dei modelli di Scala, una funzionalità imminente, lo renderebbe più conciso. Ma questo sarebbe un brutto trucco.
Se sei molto sicuro che list.size <23 usalo:
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)
Questo può essere fatto anche shapeless
con meno boilerplate utilizzando 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))
È indipendente dai tipi: ottieni None
, se la dimensione della tupla non è corretta, ma la dimensione della tupla deve essere letterale o final val
(per essere convertibile in shapeless.Nat
).
Utilizzo della corrispondenza del modello:
val intTuple = List (1,2,3) match {case List (a, b, c) => (a, b, c)}
List
/ Tuple
è una costante nota.
2015 post. Perché la risposta di Tom Crockett sia più chiarificatrice , ecco un esempio reale.
All'inizio mi sono confuso. Perché vengo da Python, dove puoi semplicemente fare tuple(list(1,2,3))
.
È a corto di linguaggio Scala? (la risposta è: non si tratta di Scala o Python, si tratta di tipo statico e tipo dinamico.)
Questo mi fa provare a trovare il punto cruciale per cui Scala non può farlo.
L'esempio di codice seguente implementa un toTuple
metodo, che dispone di type-safe toTupleN
e type-unsafe toTuple
.
Il toTuple
metodo ottiene le informazioni sulla lunghezza del tipo in fase di esecuzione, cioè nessuna informazione sulla lunghezza del tipo in fase di compilazione, quindi il tipo restituito Product
è molto simile a quello di Python tuple
(nessun tipo in ogni posizione e nessuna lunghezza dei tipi).
In questo modo si verifica un errore di runtime come la mancata corrispondenza del tipo o IndexOutOfBoundException
. (quindi la comoda lista da tupla di Python non è un pranzo gratuito.)
Al contrario, sono le informazioni sulla lunghezza fornite dall'utente che rendono toTupleN
sicuro il tempo di compilazione.
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!
puoi farlo anche tu
iterando l'elenco e applicando ogni elemento uno per uno.
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)]
def toTuple(x: List[Int]): R
, quale dovrebbe essere il tipo di R?