Преобразовать список 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]. Причина, по которой кортежи имеют это ограничение, заключается в том, что они должны иметь возможность обрабатывать неоднородные типы.


Есть ли список элементов одного и того же типа фиксированного размера? Не импортировать нестандартные библиотеки, вроде shapeless, конечно.

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

Это будет «кортеж» элементов одного типа, например, я не могу применить к нему карту.

1
@davips, как и в случае с любым новым типом данных, вам нужно будет определить, как mapдля него работает и т. д.
Том Крокетт

1
Такое ограничение кажется ярким показателем бесполезности безопасности типов больше, чем что-либо еще. Это напоминает мне цитату «пустой набор имеет много интересных свойств».
Эли

58

Вы можете сделать это с помощью экстракторов scala и сопоставления с образцом ( ссылка ):

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 во время выполнения. Если хотите, можете попробовать перехватить эту ошибку и что-нибудь предпринять, если у List неправильная длина. Еще одна приятная особенность этого решения заключается в том, что вывод не обязательно должен быть кортежем, это может быть один из ваших собственных пользовательских классов или какое-то преобразование чисел. Я не думаю, что вы можете сделать это с не-списками (так что вам может потребоваться выполнить операцию .toList для ввода).
Джим Пиварски

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

48

пример использования shapeless :

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 изменил синтаксис. Вот обновленное решение с использованием 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

Основная проблема в том, что тип для .toHList нужно указывать заранее. В более общем плане, поскольку кортежи ограничены по своей арности, для проектирования вашего программного обеспечения может быть лучше другое решение.

Тем не менее, если вы создаете список статически, рассмотрите решение, подобное этому, также с использованием shapeless. Здесь мы создаем HList напрямую, и тип доступен во время компиляции. Помните, что HList имеет функции как типа List, так и типа Tuple. т.е. он может иметь элементы с разными типами, такие как Tuple, и может отображаться среди других операций, таких как стандартные коллекции. К спискам H нужно время, чтобы привыкнуть к ним, так что действуйте медленно, если вы новичок.

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. Ограничение класса case было снято в 2.11. https://github.com/scala/scala/pull/2305

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


Будет ли результирующий тип этой гипотетической функции Any?

Ограничение класса кейса было снято в 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, по крайней мере, для
shapeless_2.11

@kfkhalili Да, и это не бесформенное ограничение. Сама Scala 2 не позволяет создавать кортежи с более чем 22 элементами, поэтому невозможно преобразовать список большего размера в кортеж с помощью любого метода.
Колмар

4

Использование сопоставления с образцом:

val intTuple = List (1,2,3) соответствует {case List (a, b, c) => (a, b, c)}


Отвечая на этот старый (более 6 лет) вопрос, часто бывает полезно указать, чем ваш ответ отличается от всех (12) старых ответов, которые уже были отправлены. В частности, ваш ответ применим только в том случае, если длина List/ Tupleявляется известной константой.
jwvh

Спасибо @ jwvh, рассмотрим ваши комментарии в будущем.
Майкл Олафисой

2

Пост 2015 г. Чтобы ответ Тома Крокетта был более проясненным , вот реальный пример.

Сначала запуталась. Потому что я пришел с Python, где вы можете просто делать tuple(list(1,2,3)).
Не хватает языка Scala? (ответ - это не о Scala или Python, а о статическом и динамическом типах.)

Это заставляет меня пытаться понять, почему Scala не может этого сделать.


В следующем примере кода реализуется 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!

Выглядит неплохо -
попробуй прямо

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.