Convertire una lista Scala in una tupla?


88

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?


1
Non è possibile (tranne "a mano") AFAIK. Dato def toTuple(x: List[Int]): R, quale dovrebbe essere il tipo di R?

7
Se non è necessario farlo per una tupla di dimensioni arbitrarie (cioè come un metodo ipotetico presentato sopra), considera 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]

2
Anche se ciò che cerchi di fare non è possibile con List, potresti esaminare il HListtipo di Shapeless , che consente la conversione da e verso le tuple ( github.com/milessabin/… ). Forse è applicabile al tuo caso d'uso.

Risposte:


59

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.


Esiste un elenco di elementi a dimensione fissa con lo stesso tipo? Non importare librerie non standard, come informe, ovviamente.

1
@davips non che io sappia, ma è abbastanza facile crearne uno tuo, ad esempiocase class Vec3[A](_1: A, _2: A, _3: A)
Tom Crockett,

Questa sarebbe una "tupla" di elementi con lo stesso tipo, ad esempio non posso applicare la mappa.

1
@davips come con qualsiasi nuovo tipo di dati che dovresti definire come mapfunziona ecc.
Tom Crockett

1
Questo tipo di limitazione sembra essere un indicatore clamoroso dell'inutilità della sicurezza dei tipi più che altro. Mi ricorda la citazione "l'insieme vuoto ha molte proprietà interessanti".
ely

58

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

4
Nel contesto di questa soluzione, i reclami sulla sicurezza dei tipi indicano che potresti ottenere un MatchError in fase di esecuzione. Se vuoi, puoi provare a catturare l'errore e fare qualcosa se l'elenco ha la lunghezza sbagliata. Un'altra caratteristica interessante di questa soluzione è che l'output non deve essere una tupla, può essere una delle tue classi personalizzate o una trasformazione dei numeri. Non penso che tu possa farlo con non-Lists, però (quindi potresti dover eseguire un'operazione .toList sull'input).
Jim Pivarski,

Puoi farlo con qualsiasi tipo di sequenza Scala usando la sintassi +:. Inoltre, non dimenticare di aggiungere un catch-all
ig-dev

48

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 toHListmetodo


18

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)

13

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

10

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.


@javadba stavo cercando come implementare listToTuple () ma alla fine non ne ho avuto bisogno. La lista dello zucchero sintattico ha risolto il mio problema.
Peter L

C'è anche un'altra sintassi, che a mio parere sembra un po 'migliore:val List(c1, c2, c3) = myList
Kolmar

7

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.


Il tipo risultante di questa funzione ipotetica sarebbe Any?

Il limite della classe del caso è stato rimosso in 2.11 github.com/scala/scala/pull/2305
gdoubleod

5

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)

5

Questo può essere fatto anche shapelesscon 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).


Questo non sembra funzionare per taglie maggiori di 22, almeno per shapeless_2.11: 2.3.3
kfkhalili

@kfkhalili Sì, e non è una limitazione informe. La stessa Scala 2 non consente tuple con più di 22 elementi, quindi non è possibile convertire un elenco di dimensioni maggiori in una tupla utilizzando alcun metodo.
Kolmar

4

Utilizzo della corrispondenza del modello:

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


Quando si risponde a una domanda così vecchia (più di 6 anni) è spesso una buona idea sottolineare come la tua risposta sia diversa da tutte le (12) risposte precedenti già inviate. In particolare, la tua risposta è applicabile solo se la lunghezza della List/ Tupleè una costante nota.
jwvh

Grazie @ jwvh, prenderò in considerazione i tuoi commenti in futuro.
Michael Olafisoye

2

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 toTuplemetodo, che dispone di type-safe toTupleNe type-unsafe toTuple.

Il toTuplemetodo 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 toTupleNsicuro 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!

Sembra carino -
provalo

2

puoi farlo anche tu

  1. tramite pattern-matching (cosa non vuoi) o
  2. 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)]
    

1

per quanto hai il tipo:

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

def doSomething(a:Int *)

doSomething(x:_*)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.