¿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?
¿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?
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]
List
, podría buscar en el HList
tipo Shapeless , que permite la conversión hacia y desde tuplas ( github.com/milessabin/… ). Quizás sea aplicable a su caso de uso.
Respuestas:
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.
case class Vec3[A](_1: A, _2: A, _3: A)
map
etc., funciona para él
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)
}
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 toHList
método
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)
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
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.
val List(c1, c2, c3) = myList
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.
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)
Esto también se puede hacer shapeless
con 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
).
Uso de la coincidencia de patrones:
val intTuple = List (1,2,3) match {case List (a, b, c) => (a, b, c)}
List
/ Tuple
es una constante conocida.
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 toTuple
método, que tiene tipo seguro toTupleN
y tipo inseguro toTuple
.
El toTuple
mé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 Product
es 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 toTupleN
tiempo 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!
puedes hacer esto
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)]
def toTuple(x: List[Int]): R
, ¿cuál debería ser el tipo de R?