Konwertować listę Scala na krotkę?

88

Jak przekonwertować listę zawierającą (powiedzmy) 3 elementy na krotkę o rozmiarze 3?

Na przykład, powiedzmy, że mam val x = List(1, 2, 3)i chcę przekonwertować to na (1, 2, 3). Jak mogę to zrobić?

grautur
źródło
1
Nie jest to możliwe (z wyjątkiem „ręcznego”) AFAIK. Biorąc pod uwagę def toTuple(x: List[Int]): R, jaki powinien być typ R?
7
Jeśli nie trzeba tego robić dla krotki o dowolnym rozmiarze (np. Jak w przypadku hipotetycznej metody przedstawionej powyżej), rozważ x match { case a :: b :: c :: Nil => (a, b, c); case _ => (0, 0, 0) }i zwróć uwagę, że wynikowy typ jest ustalony naTuple3[Int,Int,Int]
2
Chociaż to, co chcesz zrobić, nie jest możliwe List, możesz przyjrzeć się typowi bezkształtnemu HList, który umożliwia konwersję do iz krotek ( github.com/milessabin/ ... ). Może ma to zastosowanie do twojego przypadku użycia.

Odpowiedzi:

59

Nie możesz tego zrobić w bezpieczny sposób. Czemu? Ponieważ ogólnie nie możemy znać długości listy do czasu wykonania. Ale „długość” krotki musi być zakodowana w jej typie, a zatem znana w czasie kompilacji. Na przykład (1,'a',true)ma typ (Int, Char, Boolean), dla którego jest cukier Tuple3[Int, Char, Boolean]. Powodem, dla którego krotki mają to ograniczenie, jest to, że muszą być w stanie obsłużyć niejednorodne typy.

Tom Crockett
źródło
Czy istnieje lista elementów tego samego typu o stałym rozmiarze? Oczywiście nie importowanie niestandardowych bibliotek, takich jak bezkształtne.
1
@davips nie jest to, o czym wiem, ale łatwo jest zrobić własne, np.case class Vec3[A](_1: A, _2: A, _3: A)
Tom Crockett
To byłaby „krotka” elementów tego samego typu, na przykład nie mogę zastosować do niej mapy.
1
@davips, tak jak w przypadku każdego nowego typu danych, musisz zdefiniować, jak mapdziała w nim itp.
Tom Crockett
1
Tego rodzaju ograniczenie wydaje się być wyraźnym wskaźnikiem bezużyteczności bezpieczeństwa typu bardziej niż cokolwiek innego. Przypomina mi cytat „pusty zestaw ma wiele ciekawych właściwości”.
ely
58

Możesz to zrobić za pomocą ekstraktorów scala i dopasowywania wzorców ( link ):

val x = List(1, 2, 3)

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

Co zwraca krotkę

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

Możesz także użyć operatora wieloznacznego, jeśli nie masz pewności co do rozmiaru listy

val t = x match {
  case List(a, b, c, _*) => (a, b, c)
}
cyrillk
źródło
4
W kontekście tego rozwiązania skargi dotyczące bezpieczeństwa typów oznaczają, że możesz otrzymać błąd MatchError w czasie wykonywania. Jeśli chcesz, możesz spróbować złapać ten błąd i zrobić coś, jeśli lista ma złą długość. Kolejną fajną cechą tego rozwiązania jest to, że wynik nie musi być krotką, może to być jedna z własnych niestandardowych klas lub transformacja liczb. Nie sądzę jednak, aby można było to zrobić z elementami innymi niż Listy (więc może być konieczne wykonanie operacji .toList na wejściu).
Jim Pivarski,
Możesz to zrobić z dowolnym typem sekwencji Scala, używając składni +:. Nie zapomnij też dodać catch-all
ig-dev
48

przykład użycia bezkształtnego :

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

Uwaga: kompilator potrzebuje pewnych informacji o typie, aby przekonwertować List w HList, to powód, dla którego musisz przekazać informacje o typie do toHListmetody

evantill
źródło
18

Shapeless 2.0 zmienił część składni. Oto zaktualizowane rozwiązanie wykorzystujące bezkształtne.

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

Głównym problemem jest to, że typ .toHList należy określić z wyprzedzeniem. Mówiąc bardziej ogólnie, ponieważ liczba krotek jest ograniczona, projekt oprogramowania może być lepiej obsługiwany przez inne rozwiązanie.

Jeśli jednak tworzysz listę statycznie, rozważ rozwiązanie takie jak to, również wykorzystujące bezkształtne. Tutaj tworzymy HList bezpośrednio, a typ jest dostępny w czasie kompilacji. Pamiętaj, że lista HList zawiera funkcje zarówno z typów List, jak i Tuple. tj. może mieć elementy o różnych typach, takich jak krotka, i może być odwzorowywany wśród innych operacji, takich jak standardowe kolekcje. HLists zajmuje trochę czasu, zanim się przyzwyczaisz, więc idź powoli, jeśli jesteś nowy.

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
źródło
13

Pomimo prostoty i braku list o dowolnej długości, jest bezpieczny, a odpowiedź w większości przypadków:

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

Inna możliwość, gdy nie chcesz nazywać listy ani jej powtarzać (mam nadzieję, że ktoś może wskazać sposób na uniknięcie części 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
źródło
10

FWIW, chciałem użyć krotki, aby zainicjalizować kilka pól i chciałem użyć cukru składniowego przypisania krotki. NA PRZYKŁAD:

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

Okazuje się, że jest też cukier syntaktyczny do przypisywania zawartości listy ...

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

Więc nie ma potrzeby stosowania krotek, jeśli masz ten sam problem.

Peter L.
źródło
@javadba Szukałem, jak zaimplementować listToTuple (), ale ostatecznie nie musiałem tego robić. Lista cukru syntaktycznego rozwiązała mój problem.
Peter L
Jest jeszcze inna składnia, która moim zdaniem wygląda nieco lepiej:val List(c1, c2, c3) = myList
Kolmar
7

Nie możesz tego zrobić w bezpieczny sposób. W Scali listy to sekwencje elementów pewnego typu o dowolnej długości . O ile system typów wie,x może to być lista o dowolnej długości.

W przeciwieństwie do tego, liczba krotek musi być znana w czasie kompilacji. Naruszałoby to gwarancje bezpieczeństwa systemu typów, aby umożliwić przypisywaniex do typu krotki.

W rzeczywistości, z przyczyn technicznych, krotki Scala były ograniczone do 22 elementów , ale limit nie istnieje już w 2.11. Limit klasy przypadku został zniesiony w 2.11 https://github.com/scala/scala/pull/2305

Byłoby możliwe ręczne zakodowanie funkcji, która konwertuje listy składające się z maksymalnie 22 elementów i zgłasza wyjątek dla większych list. Obsługa szablonów Scala, nadchodząca funkcja, uczyni to bardziej zwięzłym. Ale to byłby brzydki hack.

Ślimak mechaniczny
źródło
Czy wynikowy typ tej hipotetycznej funkcji byłby dowolny?
Limit klas przypadków został zniesiony w 2.11 github.com/scala/scala/pull/2305
gdoubleod
5

Jeśli jesteś pewien, że twoja lista.rozmiar <23, użyj jej:

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
źródło
5

Można to również zrobić shapelessprzy mniejszym schemacie, używając 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))

Jest bezpieczny dla typu: otrzymujesz None, jeśli rozmiar krotki jest nieprawidłowy, ale rozmiar krotki musi być literałem lub final val(aby można było zamienić na shapeless.Nat).

Kolmar
źródło
Wydaje się, że to nie działa dla rozmiarów większych niż 22, przynajmniej dla bezkształtnych_2.11: 2.3.3
kfkhalili
@kfkhalili Tak, i to nie jest bezkształtne ograniczenie. Sama Scala 2 nie zezwala na krotki z więcej niż 22 elementami, więc nie jest możliwe przekonwertowanie listy o większym rozmiarze na krotkę przy użyciu żadnej metody.
Kolmar
4

Korzystanie z dopasowania wzorców:

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

Michael Olafisoye
źródło
Odpowiadając na tak stare (ponad 6-letnie) pytanie, często dobrym pomysłem jest wskazanie, w jaki sposób Twoja odpowiedź różni się od wszystkich (12) starszych odpowiedzi, które zostały już przesłane. W szczególności twoja odpowiedź ma zastosowanie tylko wtedy, gdy długość List/ Tuplejest znaną stałą.
jwvh
Dziękuję @ jwvh, rozważymy Twoje komentarze w przyszłości.
Michael Olafisoye
2

Post 2015. Aby odpowiedź Toma Crocketta była bardziej klarowna , oto prawdziwy przykład.

Na początku byłem zdezorientowany. Ponieważ pochodzę z Pythona, w którym możesz to zrobić tuple(list(1,2,3)).
Czy brakuje języka Scala? (odpowiedź brzmi - nie chodzi o Scala czy Python, chodzi o typ statyczny i typ dynamiczny).

To sprawia, że ​​próbuję znaleźć sedno, dlaczego Scala nie może tego zrobić.


Poniższy przykład kodu implementuje toTuplemetodę, która ma bezpieczny toTupleNtyp i niebezpieczny typ toTuple.

toTupleSposób uzyskać informacje rodzaj długości w czasie wykonywania, czyli żadnych informacji typu długości w czasie kompilacji, więc typ zwracany jest Product, który jest bardzo podobny pytona jest tuplerzeczywiście (bez typu w każdej pozycji, a nie długości typów).
W ten sposób jest wymawiane na błąd czasu wykonywania, taki jak niezgodność typów lubIndexOutOfBoundException . (więc wygodna lista do krotki w Pythonie nie jest darmowym lunchem).

Wręcz przeciwnie, to długość informacji podanych przez użytkownika zapewnia toTupleNbezpieczeństwo kompilacji.

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 林 煒 清
źródło
Wygląda ładnie - wypróbowuję to teraz
StephenBoesch,
2

możesz to zrobić

  1. poprzez dopasowywanie wzorców (czego nie chcesz) lub
  2. iterując listę i stosując każdy element po kolei.

    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
źródło
1

o ile masz typ:

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

def doSomething(a:Int *)

doSomething(x:_*)
dzs
źródło