Ubah daftar Scala menjadi tupel?


89

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?


1
Itu tidak mungkin (kecuali "dengan tangan") AFAIK. Diberikan def toTuple(x: List[Int]): R, apa yang seharusnya menjadi tipe R?

7
Jika tidak perlu dilakukan untuk tupel berukuran sewenang-wenang (mis. Seperti metode hipotetis yang disajikan di atas), pertimbangkan x match { case a :: b :: c :: Nil => (a, b, c); case _ => (0, 0, 0) }dan catat jenis yang dihasilkan ditetapkan diTuple3[Int,Int,Int]

2
Meskipun apa yang ingin Anda lakukan tidak memungkinkan List, Anda dapat melihat HListtipe Shapeless ' , yang memungkinkan konversi ke dan dari tupel ( github.com/milessabin/… ). Mungkin itu berlaku untuk kasus penggunaan Anda.

Jawaban:


59

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.


Apakah ada daftar elemen berukuran tetap dengan tipe yang sama? Tidak mengimpor pustaka yang tidak standar, seperti tidak berbentuk, tentunya.

1
@davips bukan yang saya tahu, tapi cukup mudah untuk membuatnya sendiri, misalnyacase class Vec3[A](_1: A, _2: A, _3: A)
Tom Crockett

Ini akan menjadi "tuple" elemen dengan tipe yang sama, saya tidak bisa menerapkan peta di atasnya, misalnya.

1
@davips seperti halnya tipe data baru Anda harus menentukan bagaimana mapdll bekerja untuknya
Tom Crockett

1
Batasan semacam ini tampaknya menjadi indikator kuat dari tidak bergunanya keamanan tipe lebih dari apapun. Ini mengingatkan saya pada kutipan "himpunan kosong memiliki banyak properti menarik."
ely

58

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

4
Dalam konteks solusi ini, keluhan tentang type-safety berarti Anda mungkin mendapatkan MatchError saat runtime. Jika mau, Anda dapat mencoba menangkap kesalahan itu dan melakukan sesuatu jika Panjang Daftar salah. Fitur bagus lainnya dari solusi ini adalah bahwa hasilnya tidak harus berupa tupel, ini bisa berupa salah satu kelas khusus Anda sendiri atau beberapa transformasi angka. Saya tidak berpikir Anda dapat melakukan ini dengan non-List, meskipun (jadi Anda mungkin perlu melakukan operasi .toList ke input).
Jim Pivarski

Anda dapat melakukan ini dengan semua jenis urutan Scala menggunakan sintaks + :. Selain itu, jangan lupa untuk menambahkan tujuan umum
ig-dev

48

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 toHListmetode


19

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)

13

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

10

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.


@javadba Saya sedang mencari cara menerapkan listToTuple () tetapi akhirnya tidak perlu. Daftar gula sintaksis memecahkan masalah saya.
Peter L

Ada juga sintaks lain, yang menurut saya terlihat sedikit lebih baik:val List(c1, c2, c3) = myList
Kolmar

7

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.


Apakah jenis yang dihasilkan dari fungsi hipotetis ini adalah Any?

Batas kelas kasus telah dicabut di 2.11 github.com/scala/scala/pull/2305
gdoubleod

5

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)

5

Ini juga dapat dilakukan shapelessdengan 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).


Tampaknya ini tidak berfungsi untuk ukuran yang lebih besar dari 22, setidaknya untuk tak berbentuk_2.11: 2.3.3
kfkhalili

@kfkhalili Ya, dan ini bukan batasan tak berbentuk. Scala 2 sendiri tidak mengizinkan tupel dengan lebih dari 22 elemen, jadi tidak mungkin untuk mengonversi Daftar dengan ukuran lebih besar ke Tupel menggunakan metode apa pun.
Kolmar

4

Menggunakan Pencocokan Pola:

val intTuple = List(1,2,3) match {case List(a, b, c) => (a, b, c)}

Saat menjawab pertanyaan yang sudah berumur (lebih dari 6 tahun) ini seringkali merupakan ide yang baik untuk menunjukkan bagaimana jawaban Anda berbeda dari semua (12) jawaban lama yang sudah dikirimkan. Secara khusus, jawaban Anda hanya berlaku jika panjang List/ Tupleadalah konstanta yang diketahui.
jwvh

Terima kasih @ jwvh, akan mempertimbangkan komentar Anda ke depannya.
Michael Olafisoye

2

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 toTuplemetode, yang memiliki tipe-aman toTupleNdan tipe-tidak aman toTuple.

The toTupleMetode mendapatkan informasi jenis-panjang pada saat run-time, yaitu tidak ada informasi jenis-panjang pada saat kompilasi, sehingga jenis kembali adalah Productyang sangat seperti Python tuplememang (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 toTupleNwaktu 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!

Kelihatannya bagus - mencobanya sekarang
StephenBoesch

2

kamu juga bisa melakukan ini

  1. melalui pencocokan pola (apa yang tidak Anda inginkan) atau
  2. 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)]
    

1

sejauh Anda memiliki tipe:

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

def doSomething(a:Int *)

doSomething(x:_*)
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.