Converter uma lista Scala em uma tupla?

89

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?

Grautur
fonte
1
Não é possível (exceto "à mão") AFAIK. Dado def toTuple(x: List[Int]): R, qual deve ser o tipo de R?
7
Se não precisar ser feito para uma tupla de tamanho arbitrário (ou seja, como um método hipotético apresentado acima), considere 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]
2
Embora o que você procura fazer não seja possível List, você pode olhar para o HListtipo Shapeless , que permite a conversão de e para tuplas ( github.com/milessabin/… ). Talvez seja aplicável ao seu caso de uso.

Respostas:

59

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.

Tom Crockett
fonte
Existe alguma lista de tamanho fixo de elementos com o mesmo tipo? Não importando bibliotecas fora do padrão, como o shapeless, é claro.
1
@davips não que eu saiba, mas é fácil o suficiente para fazer o seu próprio, por exemplocase class Vec3[A](_1: A, _2: A, _3: A)
Tom Crockett
Isso seria uma "tupla" de elementos do mesmo tipo, não posso aplicar map nela, por exemplo.
1
@davips como acontece com qualquer novo tipo de dados, você terá que definir como mapetc. funciona para ele
Tom Crockett
1
Esse tipo de limitação parece ser um indicador retumbante da inutilidade do tipo de segurança, mais do que qualquer outra coisa. Isso me lembra a citação "o conjunto vazio tem muitas propriedades interessantes."
ely
58

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)
}
Cirillk
fonte
4
No contexto desta solução, as reclamações sobre segurança de tipo significam que você pode obter um MatchError em tempo de execução. Se quiser, você pode tentar detectar esse erro e fazer algo se a lista tiver o comprimento errado. Outro recurso interessante dessa solução é que a saída não precisa ser uma tupla, pode ser uma de suas próprias classes personalizadas ou alguma transformação dos números. Eu não acho que você pode fazer isso com não-listas, embora (então você pode precisar fazer uma operação .toList para a entrada).
Jim Pivarski
Você pode fazer isso com qualquer tipo de sequência Scala usando a sintaxe +:. Além disso, não se esqueça de adicionar um pega-tudo
ig-dev
48

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 toHListmétodo

Evantill
fonte
19

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)
virtualirfan
fonte
13

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
cs95
fonte
10

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.

Peter L
fonte
@javadba Eu estava procurando como implementar listToTuple () mas acabei não precisando. A lista sintática de açúcar resolveu meu problema.
Peter L
Há também outra sintaxe, que na minha opinião parece um pouco melhor:val List(c1, c2, c3) = myList
Kolmar
7

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, xpode 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 xa 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.

Caracol mecânico
fonte
O tipo resultante dessa função hipotética seria Qualquer?
O limite da classe de caso foi levantado em 2.11 github.com/scala/scala/pull/2305
gdoubleod
5

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)
Spark-Beginner
fonte
5

Isso também pode ser feito shapelesscom 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).

Kolmar
fonte
Isso não parece funcionar para tamanhos maiores que 22, pelo menos para shapeless_2.11: 2.3.3
kfkhalili
@kfkhalili Sim, e não é uma limitação sem forma. O Scala 2 em si não permite tuplas com mais de 22 elementos, portanto não é possível converter uma lista de tamanho maior em uma tupla usando nenhum método.
Kolmar
4

Usando a correspondência de padrões:

val intTuple = List(1,2,3) match {case List(a, b, c) => (a, b, c)}
Michael Olafisoye
fonte
Ao responder uma pergunta tão antiga (mais de 6 anos), é sempre uma boa ideia apontar como sua resposta é diferente de todas as (12) respostas mais antigas já enviadas. Em particular, sua resposta só é aplicável se o comprimento de List/ Tuplefor uma constante conhecida.
jwvh
Obrigado @ jwvh, considerarei seus comentários daqui para frente.
Michael Olafisoye
2

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 toTuplemétodo, que possui tipo seguro toTupleNe tipo não seguro toTuple.

O toTuplemé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 toTupleNtempo 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!
WeiChing 林 煒 清
fonte
Parece bom - testando agora
StephenBoesch
2

você pode fazer isso também

  1. via correspondência de padrões (o que você não quer) ou
  2. 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)]
    
comonad
fonte
1

tanto quanto você tem o tipo:

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

def doSomething(a:Int *)

doSomething(x:_*)
dzs
fonte