Як я можу перетворити список із (скажімо) 3 елементами в кортеж розміром 3?
Наприклад, припустимо, що я маю, val x = List(1, 2, 3)
і я хочу перетворити це на (1, 2, 3)
. Як я можу це зробити?
Як я можу перетворити список із (скажімо) 3 елементами в кортеж розміром 3?
Наприклад, припустимо, що я маю, val x = List(1, 2, 3)
і я хочу перетворити це на (1, 2, 3)
. Як я можу це зробити?
x match { case a :: b :: c :: Nil => (a, b, c); case _ => (0, 0, 0) }
та зверніть увагу, що результуючий тип зафіксовано наTuple3[Int,Int,Int]
List
, ви можете розглянути HList
тип Shapeless , який дозволяє конвертувати в кортежі та з них ( github.com/milessabin/… ). Можливо, це застосовується для вашого випадку використання.
Відповіді:
Ви не можете зробити це безпечним способом. Чому? Тому що загалом ми не можемо знати довжину списку до часу виконання. Але "довжина" кортежу повинна бути закодована у своєму типі і, отже, відома під час компіляції. Наприклад, (1,'a',true)
має тип (Int, Char, Boolean)
, яким є цукор Tuple3[Int, Char, Boolean]
. Причина, чому кортежі мають це обмеження, полягає в тому, що вони повинні мати можливість обробляти неоднорідні типи.
case class Vec3[A](_1: A, _2: A, _3: A)
map
і т.д. для нього працює
Ви можете зробити це за допомогою екстракторів масштабу та збігу шаблонів ( посилання ):
val x = List(1, 2, 3)
val t = x match {
case List(a, b, c) => (a, b, c)
}
Що повертає кортеж
t: (Int, Int, Int) = (1,2,3)
Крім того, ви можете використовувати оператор підстановки, якщо не впевнені щодо розміру Списку
val t = x match {
case List(a, b, c, _*) => (a, b, c)
}
приклад використання безформного :
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
Примітка: компілятору потрібні деякі відомості про тип, щоб перетворити Список у списку HList, що є причиною того, чому вам потрібно передати інформацію про тип до toHList
методу
Shapeless 2.0 змінив синтаксис. Ось оновлене рішення з використанням безформного.
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
Основна проблема полягає в тому, що тип .toHList повинен бути вказаний заздалегідь. Взагалі кажучи, оскільки кортежі обмежені за своєю сутністю, дизайну вашого програмного забезпечення може бути краще надано іншим рішенням.
Однак, якщо ви створюєте список статично, розгляньте рішення, подібне до цього, також використовуючи безформне. Тут ми створюємо HList безпосередньо, і тип доступний під час компіляції. Пам'ятайте, що список HList має функції як із списку, так і з Tuple. тобто він може мати елементи різних типів, таких як Tuple, і може бути відображений серед інших операцій, таких як стандартні колекції. Списки HL займають трохи часу, щоб звикнути, хоча так повільно ступайте, якщо ви новачок.
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)
Незважаючи на простоту і не для списків будь-якої довжини, він безпечний для типу, і відповідь у більшості випадків:
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))
Інша можливість, коли ви не хочете називати список або повторювати його (сподіваюся, хтось може показати спосіб уникнути частин 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, я хотів, щоб кортеж ініціалізував низку полів, і хотів використовувати синтаксичний цукор присвоєння кортежу. Наприклад:
val (c1, c2, c3) = listToTuple(myList)
Виявляється, є синтаксичний цукор і для призначення вмісту списку ...
val c1 :: c2 :: c3 :: Nil = myList
Тож не потрібно кортежів, якщо у вас така сама проблема.
val List(c1, c2, c3) = myList
Ви не можете зробити це безпечним для типу способом. У Scala списки - це послідовності довільної довжини елементів певного типу. Наскільки відомо системі типів, це x
може бути список довільної довжини.
На відміну від цього, цілість кортежу повинна бути відома під час компіляції. Це порушило б гарантії безпеки системи типу, щоб дозволити присвоєння x
типу кортеж.
Насправді з технічних причин кортежі Scala обмежувались 22 елементами , але обмеження більше не існує в 2.11 Обмеження класу справи було скасовано в 2.11 https://github.com/scala/scala/pull/2305
Можна було б вручну кодувати функцію, яка перетворює списки до 22 елементів і створює виняток для більших списків. Підтримка шаблонів Scala - майбутня функція зробить це більш стислим. Але це було б потворно зламати.
Якщо ви впевнені, що ваш list.size <23, використовуйте його:
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)
Це також можна зробити shapeless
за меншої кількості шаблонів, використовуючи 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))
Це безпечно для типу: ви отримуєте None
, якщо розмір кортежу неправильний, але розмір кортежу повинен бути буквальним або final val
(щоб бути конвертованим shapeless.Nat
).
Використання відповідності шаблонів:
val intTuple = збіг списку (1,2,3) {Список справ (a, b, c) => (a, b, c)}
List
/ Tuple
- відома константа.
2015 пост. Щоб відповідь Тома Крокетта була більш уточнюючою , ось реальний приклад.
Спочатку я заплутався у цьому. Тому що я родом з Python, де ви можете просто робити tuple(list(1,2,3))
.
Не вистачає мови Scala? (відповідь - це не про Scala або Python, це про статичний та динамічний типи.)
Це змушує мене намагатися знайти суть, чому Скала не може цього зробити.
Наступний приклад коду реалізує toTuple
метод, який має безпечний для toTupleN
типу та тип-небезпечний toTuple
.
toTuple
Спосіб отримати інформацію типу довжиною під час виконання, тобто інформації про типі довжиною під час компіляції, так що повертається тип , Product
який дуже схожі на Пайтон в tuple
насправді (без типу в кожному положенні, і не довжинами типів).
Цей спосіб схильний до помилок виконання, таких як невідповідність типу або IndexOutOfBoundException
. (тому зручний список для кортежу Python - це не безкоштовний обід.)
Навпаки, саме інформація про довжину, яку надає користувач, робить час toTupleN
компіляції безпечним.
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!
Ви можете зробити це або
перебираючи список і застосовуючи кожен елемент по одному.
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
, яким повинен бути тип R?