Como posso converter uma lista com (digamos) 3 elementos em uma tupla de tamanho 3?
Por exemplo, digamos que eu tenho val x = List(1, 2, 3)
e quero converter isso em (1, 2, 3)
. Como posso fazer isso?
Como posso converter uma lista com (digamos) 3 elementos em uma tupla de tamanho 3?
Por exemplo, digamos que eu tenho val x = List(1, 2, 3)
e quero converter isso em (1, 2, 3)
. Como posso fazer isso?
x match { case a :: b :: c :: Nil => (a, b, c); case _ => (0, 0, 0) }
e observe que o tipo resultante é fixado emTuple3[Int,Int,Int]
List
, você pode olhar para o HList
tipo Shapeless , que permite a conversão de e para tuplas ( github.com/milessabin/… ). Talvez seja aplicável ao seu caso de uso.
Respostas:
Você não pode fazer isso de uma forma segura. Por quê? Porque, em geral, não podemos saber o comprimento de uma lista até o tempo de execução. Mas o "comprimento" de uma tupla deve ser codificado em seu tipo e, portanto, conhecido em tempo de compilação. Por exemplo, (1,'a',true)
tem o tipo (Int, Char, Boolean)
, que é açúcar para Tuple3[Int, Char, Boolean]
. A razão pela qual as tuplas têm essa restrição é que elas precisam ser capazes de lidar com tipos não homogêneos.
case class Vec3[A](_1: A, _2: A, _3: A)
map
etc. funciona para ele
Você pode fazer isso usando extratores de scala e correspondência de padrões ( link ):
val x = List(1, 2, 3)
val t = x match {
case List(a, b, c) => (a, b, c)
}
Que retorna uma tupla
t: (Int, Int, Int) = (1,2,3)
Além disso, você pode usar um operador curinga se não tiver certeza sobre o tamanho da lista
val t = x match {
case List(a, b, c, _*) => (a, b, c)
}
um exemplo usando sem forma :
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: o compilador precisa de algumas informações de tipo para converter a lista na HList que é o motivo pelo qual você precisa passar informações de tipo para o toHList
método
Shapeless 2.0 mudou algumas sintaxes. Aqui está a solução atualizada usando o 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
O principal problema é que o tipo de .toHList deve ser especificado com antecedência. De forma mais geral, como as tuplas são limitadas em sua aridade, o design do seu software pode ser melhor servido por uma solução diferente.
Ainda assim, se você estiver criando uma lista estaticamente, considere uma solução como esta, também usando shapeless. Aqui, criamos uma HList diretamente e o tipo está disponível em tempo de compilação. Lembre-se de que uma HList possui recursos dos tipos Lista e Tupla. ou seja, pode ter elementos com diferentes tipos, como uma tupla, e pode ser mapeado entre outras operações, como coleções padrão. Listas demoram um pouco para se acostumar, então vá devagar se você for novo.
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)
Apesar da simplicidade e de não ser para listas de qualquer tamanho, é seguro para tipos e a resposta na maioria dos 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))
Outra possibilidade, quando você não quiser nomear a lista nem repeti-la (espero que alguém mostre uma forma de evitar as partes 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, eu queria uma tupla para inicializar vários campos e queria usar o açúcar sintático da atribuição de tupla. POR EXEMPLO:
val (c1, c2, c3) = listToTuple(myList)
Acontece que existe açúcar sintático para atribuir o conteúdo de uma lista também ...
val c1 :: c2 :: c3 :: Nil = myList
Portanto, não há necessidade de tuplas se você tiver o mesmo problema.
val List(c1, c2, c3) = myList
Você não pode fazer isso de maneira segura. No Scala, as listas são sequências de comprimento arbitrário de elementos de algum tipo. Pelo que o sistema de tipos sabe, x
pode ser uma lista de comprimento arbitrário.
Em contraste, a aridade de uma tupla deve ser conhecida em tempo de compilação. Isso violaria as garantias de segurança do sistema de tipos permitir a atribuição x
a um tipo de tupla.
Na verdade, por razões técnicas, as tuplas de Scala eram limitadas a 22 elementos , mas o limite não existe mais em 2.11. O limite de classe de caso foi levantado em 2.11 https://github.com/scala/scala/pull/2305
Seria possível codificar manualmente uma função que converte listas de até 22 elementos e lança uma exceção para listas maiores. O suporte a modelos do Scala, um recurso futuro, tornaria isso mais conciso. Mas isso seria um hack feio.
Se você tiver certeza de que list.size <23, use-o:
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)
Isso também pode ser feito shapeless
com menos clichê 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))
É seguro para tipos: você obtém None
, se o tamanho da tupla estiver incorreto, mas o tamanho da tupla deve ser literal ou final val
(para ser conversível em shapeless.Nat
).
Usando a correspondência de padrões:
val intTuple = List(1,2,3) match {case List(a, b, c) => (a, b, c)}
List
/ Tuple
for uma constante conhecida.
Postagem de 2015. Para que a resposta de Tom Crockett seja mais esclarecedora , aqui está um exemplo real.
No começo, fiquei confuso sobre isso. Porque eu venho de Python, onde você pode simplesmente fazer tuple(list(1,2,3))
.
É curto a linguagem Scala? (a resposta é - não é sobre Scala ou Python, é sobre tipo estático e tipo dinâmico.)
Isso me faz tentar encontrar o ponto crucial por que Scala não pode fazer isso.
O exemplo de código a seguir implementa um toTuple
método, que possui tipo seguro toTupleN
e tipo não seguro toTuple
.
O toTuple
método obtém as informações de comprimento de tipo em tempo de execução, ou seja, nenhuma informação de comprimento de tipo em tempo de compilação, então o tipo de retorno Product
é muito parecido com o do Python tuple
(nenhum tipo em cada posição e nenhum comprimento de tipos).
Dessa forma, está sujeito a erros de tempo de execução como incompatibilidade de tipo ou IndexOutOfBoundException
. (portanto, a conveniente lista-a-tupla do Python não é um almoço grátis.)
Ao contrário, é a informação de comprimento fornecida pelo usuário que torna o toTupleN
tempo de compilação 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!
você pode fazer isso também
iterando pela lista e aplicando cada elemento um por um.
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
, qual deve ser o tipo de R?