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ć?
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ć?
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]
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:
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.
case class Vec3[A](_1: A, _2: A, _3: A)
map
działa w nim itp.
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)
}
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 toHList
metody
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)
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
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.
val List(c1, c2, c3) = myList
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.
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)
Można to również zrobić shapeless
przy 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
).
Korzystanie z dopasowania wzorców:
val intTuple = List (1,2,3) match {case List (a, b, c) => (a, b, c)}
List
/ Tuple
jest znaną stałą.
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 toTuple
metodę, która ma bezpieczny toTupleN
typ i niebezpieczny typ toTuple
.
toTuple
Sposó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 tuple
rzeczywiś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 toTupleN
bezpieczeń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!
możesz to zrobić
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)]
def toTuple(x: List[Int]): R
, jaki powinien być typ R?