Перетворити список Scala на кортеж?


88

Як я можу перетворити список із (скажімо) 3 елементами в кортеж розміром 3?

Наприклад, припустимо, що я маю, val x = List(1, 2, 3)і я хочу перетворити це на (1, 2, 3). Як я можу це зробити?


1
Це неможливо (крім "вручну") AFAIK. Враховуючи def toTuple(x: List[Int]): R, яким повинен бути тип R?

7
Якщо цього не потрібно робити для кортежу довільного розміру (тобто, як гіпотетичний метод, представлений вище), розгляньте x match { case a :: b :: c :: Nil => (a, b, c); case _ => (0, 0, 0) }та зверніть увагу, що результуючий тип зафіксовано наTuple3[Int,Int,Int]

2
Хоча те, що ви прагнете зробити, неможливо List, ви можете розглянути HListтип Shapeless , який дозволяє конвертувати в кортежі та з них ( github.com/milessabin/… ). Можливо, це застосовується для вашого випадку використання.

Відповіді:


59

Ви не можете зробити це безпечним способом. Чому? Тому що загалом ми не можемо знати довжину списку до часу виконання. Але "довжина" кортежу повинна бути закодована у своєму типі і, отже, відома під час компіляції. Наприклад, (1,'a',true)має тип (Int, Char, Boolean), яким є цукор Tuple3[Int, Char, Boolean]. Причина, чому кортежі мають це обмеження, полягає в тому, що вони повинні мати можливість обробляти неоднорідні типи.


Чи існує якийсь список елементів фіксованого розміру того самого типу? Не імпортуючи нестандартні бібліотеки, звичайно, як безформні.

1
@davips не те, що я знаю, але зробити це досить просто, наприкладcase class Vec3[A](_1: A, _2: A, _3: A)
Том Крокетт

Це буде "кортеж" елементів одного типу, наприклад, я не можу застосувати на ньому карту.

1
@davips, як і для будь-якого нового типу даних, вам потрібно було б визначити, як mapі т.д. для нього працює
Tom Crockett,

1
Таке обмеження, здається, є рішучим показником марності безпеки типу більше, ніж будь-що інше. Це нагадує мені цитату "порожній набір має багато цікавих властивостей".
ely,

58

Ви можете зробити це за допомогою екстракторів масштабу та збігу шаблонів ( посилання ):

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

4
У контексті цього рішення скарги на безпеку типу означають, що ви можете отримати MatchError під час виконання. Якщо ви хочете, ви можете спробувати вловити цю помилку і зробити щось, якщо Список має неправильну довжину. Ще однією приємною особливістю цього рішення є те, що результат не повинен бути кортежем, це може бути один із ваших власних класів або якесь перетворення чисел. Я не думаю, що ви можете зробити це з не-списками, однак (тому вам може знадобитися виконати операцію .toList над входом).
Jim Pivarski

Ви можете зробити це з будь-яким типом послідовності Scala, використовуючи синтаксис +:. Крім того, не забудьте додати загальний
ig-dev

48

приклад використання безформного :

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методу


18

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)

13

Незважаючи на простоту і не для списків будь-якої довжини, він безпечний для типу, і відповідь у більшості випадків:

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

10

FWIW, я хотів, щоб кортеж ініціалізував низку полів, і хотів використовувати синтаксичний цукор присвоєння кортежу. Наприклад:

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

Виявляється, є синтаксичний цукор і для призначення вмісту списку ...

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

Тож не потрібно кортежів, якщо у вас така сама проблема.


@javadba Я шукав, як реалізувати listToTuple (), але в підсумку не мав необхідності. Список синтаксичних цукрів вирішив мою проблему.
Peter L

Існує також інший синтаксис, який, на мій погляд, виглядає дещо кращим:val List(c1, c2, c3) = myList
Колмар,

7

Ви не можете зробити це безпечним для типу способом. У Scala списки - це послідовності довільної довжини елементів певного типу. Наскільки відомо системі типів, це xможе бути список довільної довжини.

На відміну від цього, цілість кортежу повинна бути відома під час компіляції. Це порушило б гарантії безпеки системи типу, щоб дозволити присвоєння xтипу кортеж.

Насправді з технічних причин кортежі Scala обмежувались 22 елементами , але обмеження більше не існує в 2.11 Обмеження класу справи було скасовано в 2.11 https://github.com/scala/scala/pull/2305

Можна було б вручну кодувати функцію, яка перетворює списки до 22 елементів і створює виняток для більших списків. Підтримка шаблонів Scala - майбутня функція зробить це більш стислим. Але це було б потворно зламати.


Чи буде результуючий тип цієї гіпотетичної функції будь-яким?

Обмеження класу справ було скасовано у 2.11 github.com/scala/scala/pull/2305
gdoubleod

5

Якщо ви впевнені, що ваш 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)

5

Це також можна зробити 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).


Здається, це не працює для розмірів, що перевищують 22, принаймні для
безформенних_2.11

@kfkhalili Так, і це не безформне обмеження. Сама Scala 2 не допускає кортежів із понад 22 елементами, тому перетворити Список більшого розміру в Кортеж неможливо будь-яким способом.
Kolmar

4

Використання відповідності шаблонів:

val intTuple = збіг списку (1,2,3) {Список справ (a, b, c) => (a, b, c)}


Відповідаючи на таке старе запитання (понад 6 років), часто є гарною ідеєю зазначити, чим ваша відповідь відрізняється від усіх (12) старих відповідей, які вже були подані. Зокрема, ваша відповідь застосовується лише у тому випадку, якщо довжина List/ Tuple- відома константа.
jwvh

Дякую @ jwvh, розглянемо ваші коментарі надалі.
Майкл Олафісоє,

2

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!

Виглядає приємно - спробувати зараз
javadba

2

Ви можете зробити це або

  1. за допомогою зіставлення зразків (те, що ви не хочете) або
  2. перебираючи список і застосовуючи кожен елемент по одному.

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

1

наскільки у вас тип:

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

def doSomething(a:Int *)

doSomething(x:_*)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.