¿Convertir una lista de Scala en una tupla?

88

¿Cómo puedo convertir una lista con (digamos) 3 elementos en una tupla de tamaño 3?

Por ejemplo, digamos que tengo val x = List(1, 2, 3)y quiero convertir esto en (1, 2, 3). ¿Cómo puedo hacer esto?

grautur
fuente
1
No es posible (excepto "a mano") AFAIK. Dado def toTuple(x: List[Int]): R, ¿cuál debería ser el tipo de R?
7
Si no es necesario hacerlo para una tupla de tamaño arbitrario (es decir, como un método hipotético presentado anteriormente), considere x match { case a :: b :: c :: Nil => (a, b, c); case _ => (0, 0, 0) }y tenga en cuenta que el tipo resultante se fija enTuple3[Int,Int,Int]
2
Si bien lo que busca hacer no es posible con List, podría buscar en el HListtipo Shapeless , que permite la conversión hacia y desde tuplas ( github.com/milessabin/… ). Quizás sea aplicable a su caso de uso.

Respuestas:

59

No puede hacer esto de forma segura. ¿Por qué? Porque, en general, no podemos saber la longitud de una lista hasta el tiempo de ejecución. Pero la "longitud" de una tupla debe codificarse en su tipo y, por tanto, conocerse en el momento de la compilación. Por ejemplo, (1,'a',true)tiene el tipo (Int, Char, Boolean), que es azúcar para Tuple3[Int, Char, Boolean]. La razón por la que las tuplas tienen esta restricción es que deben poder manejar tipos no homogéneos.

Tom Crockett
fuente
¿Existe alguna lista de elementos de tamaño fijo con el mismo tipo? No importando bibliotecas no estándar, como sin forma, por supuesto.
1
@davips no que yo sepa, pero es bastante fácil hacer el tuyo, por ejemplocase class Vec3[A](_1: A, _2: A, _3: A)
Tom Crockett
Esto sería una "tupla" de elementos con el mismo tipo, no puedo aplicarle mapa, por ejemplo.
1
@davips, como con cualquier tipo de datos nuevo, tendría que definir cómo, mapetc., funciona para él
Tom Crockett
1
Este tipo de limitación parece ser un indicador rotundo de la inutilidad de la seguridad de tipos más que cualquier otra cosa. Me recuerda a la cita "el conjunto vacío tiene muchas propiedades interesantes".
ely
58

Puede hacerlo usando extractores scala y coincidencia de patrones ( enlace ):

val x = List(1, 2, 3)

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

Que devuelve una tupla

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

Además, puede utilizar un operador comodín si no está seguro del tamaño de la lista.

val t = x match {
  case List(a, b, c, _*) => (a, b, c)
}
cyrillk
fuente
4
En el contexto de esta solución, las quejas sobre la seguridad de tipos significan que es posible que obtenga un MatchError en tiempo de ejecución. Si lo desea, puede intentar detectar ese error y hacer algo si la lista tiene la longitud incorrecta. Otra característica interesante de esta solución es que la salida no tiene que ser una tupla, puede ser una de sus propias clases personalizadas o alguna transformación de los números. Sin embargo, no creo que pueda hacer esto con no listas (por lo que es posible que deba hacer una operación .toList en la entrada).
Jim Pivarski
Puede hacer esto con cualquier tipo de secuencia Scala usando la sintaxis + :. Además, no olvide agregar un catch-all
ig-dev
48

un ejemplo 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: el compilador necesita alguna información de tipo para convertir la Lista en HList que la razón por la que necesita pasar información de tipo al toHListmétodo

evantill
fuente
18

Shapeless 2.0 cambió algo de sintaxis. Aquí está la solución actualizada usando 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

El problema principal es que el tipo de .toHList debe especificarse con anticipación. De manera más general, dado que las tuplas son limitadas en su aridad, el diseño de su software podría estar mejor servido por una solución diferente.

Aún así, si está creando una lista de forma estática, considere una solución como esta, también usando sin forma. Aquí, creamos una HList directamente y el tipo está disponible en tiempo de compilación. Recuerde que una HList tiene características de los tipos List y Tuple. es decir, puede tener elementos con diferentes tipos como una tupla y se puede mapear entre otras operaciones como colecciones estándar. Sin embargo, las listas H tardan un poco en acostumbrarse, así que pise lentamente si es nuevo.

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
fuente
13

A pesar de la simplicidad y no ser para listas de ninguna longitud, es de tipo seguro y la respuesta en la mayoría de los casos:

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

Otra posibilidad, cuando no quiere nombrar la lista ni repetirla (espero que alguien pueda mostrar una forma de evitar las partes de 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
fuente
10

FWIW, quería una tupla para inicializar varios campos y quería usar el azúcar sintáctico de la asignación de tuplas. P.EJ:

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

Resulta que también hay azúcar sintáctico para asignar el contenido de una lista ...

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

Por lo tanto, no es necesario utilizar tuplas si tiene el mismo problema.

Peter L
fuente
@javadba Estaba buscando cómo implementar listToTuple () pero terminé sin necesidad de hacerlo. La lista sintáctica del azúcar resolvió mi problema.
Peter L
También hay otra sintaxis, que en mi opinión se ve un poco mejor:val List(c1, c2, c3) = myList
Kolmar
7

No puede hacer esto de forma segura. En Scala, las listas son secuencias de elementos de algún tipo de longitud arbitraria . Por lo que sabe el sistema de tipos,x podría ser una lista de longitud arbitraria.

Por el contrario, la aridad de una tupla debe conocerse en el momento de la compilación. Violaría las garantías de seguridad del sistema de tipos para permitir la asignaciónx a un tipo de tupla.

De hecho, por razones técnicas, las tuplas de Scala se limitaron a 22 elementos , pero el límite ya no existe en 2.11. El límite de clases de casos se eliminó en 2.11. https://github.com/scala/scala/pull/2305

Sería posible codificar manualmente una función que convierta listas de hasta 22 elementos y arroje una excepción para listas más grandes. El soporte de plantillas de Scala, una característica próxima, lo haría más conciso. Pero este sería un truco feo.

Caracol mecánico
fuente
¿El tipo resultante de esta función hipotética sería Cualquiera?
El límite de clases de casos se ha eliminado en 2.11 github.com/scala/scala/pull/2305
gdoubleod
5

Si está muy seguro de que su list.size <23 úselo:

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-Beginner
fuente
5

Esto también se puede hacer shapelesscon menos repetición usando 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 seguro para los tipos: obtiene None, si el tamaño de la tupla es incorrecto, pero el tamaño de la tupla debe ser literal o final val(para ser convertible a shapeless.Nat).

Kolmar
fuente
Esto no parece funcionar para tamaños superiores a 22, al menos para
shapeless_2.11
@kfkhalili Sí, y no es una limitación informe. Scala 2 en sí no permite tuplas con más de 22 elementos, por lo que no es posible convertir una Lista de mayor tamaño en una Tupla utilizando ningún método.
Kolmar
4

Uso de la coincidencia de patrones:

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

Michael Olafisoye
fuente
Al responder una pregunta tan antigua (más de 6 años), a menudo es una buena idea señalar en qué se diferencia su respuesta de todas las (12) respuestas anteriores ya enviadas. En particular, su respuesta solo es aplicable si la longitud de List/ Tuplees una constante conocida.
jwvh
Gracias @ jwvh, considerará sus comentarios en el futuro.
Michael Olafisoye
2

Publicación de 2015. Para que la respuesta de Tom Crockett sea ​​más aclaratoria , aquí hay un ejemplo real.

Al principio, me confundí. Porque vengo de Python, donde puedes hacerlo tuple(list(1,2,3)).
¿Le falta el lenguaje Scala? (la respuesta es: no se trata de Scala o Python, se trata de tipo estático y tipo dinámico).

Eso me lleva a tratar de encontrar el quid de por qué Scala no puede hacer esto.


El siguiente ejemplo de código implementa un toTuplemétodo, que tiene tipo seguro toTupleNy tipo inseguro toTuple.

El toTuplemétodo obtiene la información de longitud de tipo en tiempo de ejecución, es decir, no hay información de longitud de tipo en tiempo de compilación, por lo que el tipo de retorno Productes muy parecido al de Python tuple(sin tipo en cada posición y sin longitud de tipos).
De esa forma, se producirá un error de tiempo de ejecución como falta de coincidencia de tipos o IndexOutOfBoundException. (por lo que la conveniente lista a tupla de Python no es un almuerzo gratis).

Por el contrario, es la información de longitud proporcionada por el usuario la que hace que el toTupleNtiempo de compilación sea seguro.

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 林 煒 清
fuente
Se ve bien, probándolo ahora
StephenBoesch
2

puedes hacer esto

  1. a través de la coincidencia de patrones (lo que no desea) o
  2. iterando a través de la lista y aplicando cada elemento uno por 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)]
    
comonad
fuente
1

hasta donde tenga el tipo:

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

def doSomething(a:Int *)

doSomething(x:_*)
dzs
fuente