Как я могу преобразовать список (скажем) из 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
для него работает и т. д.
Вы можете сделать это с помощью экстракторов 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)
}
пример использования 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
метод.
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)
Несмотря на простоту и непригодность для списков любой длины, он безопасен по типу и в большинстве случаев дает ответ:
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. Ограничение класса case было снято в 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 = List (1,2,3) соответствует {case List (a, b, c) => (a, b, c)}
List
/ Tuple
является известной константой.
Пост 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!
ты можешь сделать это либо
перебирая список и применяя каждый элемент один за другим.
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?