Bagaimana cara mengubah daftar dengan (katakanlah) 3 elemen menjadi tupel ukuran 3?
Misalnya, saya punya val x = List(1, 2, 3)
dan saya ingin mengubahnya menjadi (1, 2, 3)
. Bagaimana saya bisa melakukan ini?
Bagaimana cara mengubah daftar dengan (katakanlah) 3 elemen menjadi tupel ukuran 3?
Misalnya, saya punya val x = List(1, 2, 3)
dan saya ingin mengubahnya menjadi (1, 2, 3)
. Bagaimana saya bisa melakukan ini?
x match { case a :: b :: c :: Nil => (a, b, c); case _ => (0, 0, 0) }
dan catat jenis yang dihasilkan ditetapkan diTuple3[Int,Int,Int]
List
, Anda dapat melihat HList
tipe Shapeless ' , yang memungkinkan konversi ke dan dari tupel ( github.com/milessabin/… ). Mungkin itu berlaku untuk kasus penggunaan Anda.
Jawaban:
Anda tidak dapat melakukan ini dengan cara yang aman. Mengapa? Karena secara umum kita tidak bisa mengetahui panjang list sampai runtime. Tetapi "panjang" dari sebuah tupel harus dikodekan dalam tipenya, dan karenanya diketahui pada waktu kompilasi. Misalnya (1,'a',true)
memiliki jenis (Int, Char, Boolean)
gula untuk Tuple3[Int, Char, Boolean]
. Alasan tupel memiliki batasan ini adalah karena mereka harus dapat menangani tipe yang tidak homogen.
case class Vec3[A](_1: A, _2: A, _3: A)
map
dll bekerja untuknya
Anda dapat melakukannya menggunakan ekstraktor skala dan pencocokan pola ( tautan ):
val x = List(1, 2, 3)
val t = x match {
case List(a, b, c) => (a, b, c)
}
Yang mengembalikan tupel
t: (Int, Int, Int) = (1,2,3)
Selain itu, Anda dapat menggunakan operator karakter pengganti jika tidak yakin tentang ukuran List
val t = x match {
case List(a, b, c, _*) => (a, b, c)
}
sebuah contoh menggunakan tak berbentuk :
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
Catatan: kompilator memerlukan beberapa informasi tipe untuk mengubah Daftar di HList yang menjadi alasan mengapa Anda perlu meneruskan informasi tipe ke toHList
metode
Shapeless 2.0 mengubah beberapa sintaks. Inilah solusi yang diperbarui menggunakan tidak berbentuk.
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
Masalah utamanya adalah tipe untuk .toHList harus ditentukan sebelumnya. Secara lebih umum, karena tupel terbatas dalam aritasnya, desain perangkat lunak Anda mungkin lebih baik disajikan dengan solusi yang berbeda.
Namun, jika Anda membuat daftar secara statis, pertimbangkan solusi seperti ini, juga menggunakan tak berbentuk. Di sini, kami membuat HList secara langsung dan jenisnya tersedia pada waktu kompilasi. Ingatlah bahwa HList memiliki fitur dari tipe List dan Tuple. yaitu dapat memiliki elemen dengan tipe berbeda seperti Tuple dan dapat dipetakan di antara operasi lain seperti koleksi standar. HLists membutuhkan sedikit waktu untuk membiasakan diri, jadi melangkahlah perlahan jika Anda baru.
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)
Terlepas dari kesederhanaan dan tidak untuk daftar dengan panjang apa pun, itu aman untuk tipe dan jawabannya dalam banyak kasus:
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))
Kemungkinan lain, ketika Anda tidak ingin menamai daftar atau mengulanginya (saya harap seseorang dapat menunjukkan cara untuk menghindari Seq / bagian kepala):
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, saya ingin tupel untuk menginisialisasi sejumlah bidang dan ingin menggunakan gula sintaksis dari tugas tupel. MISALNYA:
val (c1, c2, c3) = listToTuple(myList)
Ternyata ada gula sintaksis untuk menugaskan konten daftar juga ...
val c1 :: c2 :: c3 :: Nil = myList
Jadi tidak perlu tupel jika Anda mengalami masalah yang sama.
val List(c1, c2, c3) = myList
Anda tidak dapat melakukan ini dengan cara yang aman. Di Scala, daftar adalah urutan elemen dengan panjang arbitrer dari beberapa jenis. Sejauh yang diketahui sistem tipe,x
bisa menjadi daftar panjang sewenang-wenang.
Sebaliknya, arity dari sebuah tupel harus diketahui pada waktu kompilasi. Ini akan melanggar jaminan keamanan dari sistem tipe untuk memungkinkan penetapanx
ke tipe tupel.
Faktanya, karena alasan teknis, tupel Scala dibatasi hingga 22 elemen , tetapi batas tersebut tidak lagi ada di 2.11 Batas kelas kasus telah dicabut di 2.11 https://github.com/scala/scala/pull/2305
Dimungkinkan untuk mengkodekan fungsi secara manual yang mengonversi daftar hingga 22 elemen, dan memunculkan pengecualian untuk daftar yang lebih besar. Dukungan template Scala, fitur yang akan datang, akan membuatnya lebih ringkas. Tapi ini akan menjadi peretasan yang buruk.
Jika Anda sangat yakin bahwa list.size Anda <23 gunakan:
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)
Ini juga dapat dilakukan shapeless
dengan lebih sedikit boilerplate menggunakan 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))
Ini jenis-aman: Anda mendapatkan None
, jika ukuran tupel salah, tetapi ukuran tupel harus literal atau final val
(dapat diubah shapeless.Nat
).
Menggunakan Pencocokan Pola:
val intTuple = List(1,2,3) match {case List(a, b, c) => (a, b, c)}
List
/ Tuple
adalah konstanta yang diketahui.
2015 pos. Agar jawaban Tom Crockett lebih memperjelas , berikut adalah contoh nyata.
Awalnya saya bingung. Karena saya berasal dari Python, di mana Anda bisa melakukannya tuple(list(1,2,3))
.
Apakah ini pendek dari bahasa Scala? (jawabannya adalah - ini bukan tentang Scala atau Python, ini tentang tipe statis dan tipe dinamis.)
Itulah yang menyebabkan saya mencoba menemukan inti mengapa Scala tidak dapat melakukan ini.
Contoh kode berikut mengimplementasikan sebuah toTuple
metode, yang memiliki tipe-aman toTupleN
dan tipe-tidak aman toTuple
.
The toTuple
Metode mendapatkan informasi jenis-panjang pada saat run-time, yaitu tidak ada informasi jenis-panjang pada saat kompilasi, sehingga jenis kembali adalah Product
yang sangat seperti Python tuple
memang (ada jenis pada setiap posisi, dan tidak ada panjang jenis).
Cara itu rentan terhadap error runtime seperti type-mismatch atau IndexOutOfBoundException
. (jadi list-to-tuple Python yang nyaman bukanlah makan siang gratis.)
Sebaliknya, panjang informasi yang disediakan pengguna yang membuat toTupleN
waktu kompilasi aman.
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!
kamu juga bisa melakukan ini
dengan mengulangi daftar dan menerapkan setiap elemen satu per satu.
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
, apa yang seharusnya menjadi tipe R?