Làm cách nào để chuyển đổi một danh sách có (giả sử) 3 phần tử thành một bộ kích thước 3?
Ví dụ: giả sử tôi có val x = List(1, 2, 3)
và tôi muốn chuyển nó thành (1, 2, 3)
. Tôi có thể làm cái này như thế nào?
Làm cách nào để chuyển đổi một danh sách có (giả sử) 3 phần tử thành một bộ kích thước 3?
Ví dụ: giả sử tôi có val x = List(1, 2, 3)
và tôi muốn chuyển nó thành (1, 2, 3)
. Tôi có thể làm cái này như thế nào?
x match { case a :: b :: c :: Nil => (a, b, c); case _ => (0, 0, 0) }
và lưu ý các loại quả là cố định tạiTuple3[Int,Int,Int]
List
, bạn có thể xem xét kiểu Shapeless ' HList
, cho phép chuyển đổi sang và từ các bộ giá trị ( github.com/milessabin/… ). Có thể nó áp dụng cho trường hợp sử dụng của bạn.
Câu trả lời:
Bạn không thể làm điều này theo cách an toàn. Tại sao? Bởi vì nói chung chúng ta không thể biết độ dài của một danh sách cho đến thời gian chạy. Nhưng "độ dài" của một tuple phải được mã hóa theo kiểu của nó và do đó được biết đến tại thời điểm biên dịch. Ví dụ, (1,'a',true)
có loại (Int, Char, Boolean)
, là đường cho Tuple3[Int, Char, Boolean]
. Lý do bộ giá trị có hạn chế này là chúng cần phải có khả năng xử lý các loại không đồng nhất.
case class Vec3[A](_1: A, _2: A, _3: A)
map
vv làm việc cho nó
Bạn có thể làm điều đó bằng cách sử dụng trình trích xuất tỷ lệ và đối sánh mẫu ( liên kết ):
val x = List(1, 2, 3)
val t = x match {
case List(a, b, c) => (a, b, c)
}
Trả về một tuple
t: (Int, Int, Int) = (1,2,3)
Ngoài ra, bạn có thể sử dụng toán tử ký tự đại diện nếu không chắc chắn về kích thước của Danh sách
val t = x match {
case List(a, b, c, _*) => (a, b, c)
}
một ví dụ sử dụng 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
Lưu ý: trình biên dịch cần một số thông tin kiểu để chuyển đổi Danh sách trong HList đó là lý do tại sao bạn cần chuyển thông tin kiểu cho toHList
phương thức
Shapeless 2.0 đã thay đổi một số cú pháp. Đây là giải pháp được cập nhật bằng cách sử dụng 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
Vấn đề chính là loại cho .toHList phải được chỉ định trước thời hạn. Nói chung hơn, vì các bộ giá trị có giới hạn về độ hiếm của chúng, nên thiết kế phần mềm của bạn có thể được phục vụ tốt hơn bằng một giải pháp khác.
Tuy nhiên, nếu bạn đang tạo một danh sách tĩnh, hãy xem xét một giải pháp như giải pháp này, cũng sử dụng shapeless. Ở đây, chúng tôi tạo một HList trực tiếp và loại có sẵn tại thời điểm biên dịch. Hãy nhớ rằng một HList có các tính năng từ cả hai loại Danh sách và Tuple. tức là nó có thể có các phần tử với các kiểu khác nhau như Tuple và có thể được ánh xạ giữa các hoạt động khác như tập hợp tiêu chuẩn. HLists mất một chút thời gian để làm quen với mặc dù rất chậm nếu bạn là người mới.
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)
Mặc dù đơn giản và không dành cho danh sách có độ dài bất kỳ, nhưng nó là kiểu an toàn và là câu trả lời trong hầu hết các trường hợp:
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))
Một khả năng khác, khi bạn không muốn đặt tên cho danh sách cũng như không lặp lại nó (tôi hy vọng ai đó có thể chỉ ra cách để tránh các phần 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, tôi muốn một bộ tuple để tạo ra một số trường và muốn sử dụng đường cú pháp của phép gán tuple. VÍ DỤ:
val (c1, c2, c3) = listToTuple(myList)
Nó chỉ ra rằng có một đường cú pháp để chỉ định nội dung của một danh sách ...
val c1 :: c2 :: c3 :: Nil = myList
Vì vậy, không cần bộ giá trị nếu bạn gặp vấn đề tương tự.
val List(c1, c2, c3) = myList
Bạn không thể làm điều này theo cách an toàn về loại. Trong Scala, danh sách là chuỗi các phần tử có độ dài tùy ý của một số kiểu. Theo như hệ thống loại biết, x
có thể là một danh sách có độ dài tùy ý.
Ngược lại, độ hiếm của một tuple phải được biết đến tại thời điểm biên dịch. Nó sẽ vi phạm các đảm bảo an toàn của hệ thống kiểu cho phép gán x
cho một kiểu tuple.
Trên thực tế, vì lý do kỹ thuật, bộ giá trị Scala bị giới hạn ở 22 phần tử , nhưng giới hạn không còn tồn tại trong 2.11 Giới hạn lớp trường hợp đã được dỡ bỏ trong 2.11 https://github.com/scala/scala/pull/2305
Có thể viết mã theo cách thủ công một hàm chuyển đổi danh sách lên đến 22 phần tử và ném một ngoại lệ cho các danh sách lớn hơn. Hỗ trợ mẫu của Scala, một tính năng sắp ra mắt, sẽ làm cho điều này ngắn gọn hơn. Nhưng đây sẽ là một vụ hack xấu xí.
Nếu bạn chắc chắn rằng list.size <23 của bạn, hãy sử dụng nó:
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)
Điều này cũng có thể được thực hiện shapeless
với ít tấm boiler hơn bằng cách sử dụng 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))
Đó là loại an toàn: bạn sẽ nhận được None
, nếu kích thước tuple không chính xác, nhưng kích thước tuple phải là chữ hoặc final val
(có thể chuyển đổi thành shapeless.Nat
).
Sử dụng Khớp mẫu:
val intTuple = List (1,2,3) match {case List (a, b, c) => (a, b, c)}
List
/ Tuple
là một hằng số đã biết.
2015 bài. Để câu trả lời của Tom Crockett được làm rõ hơn , đây là một ví dụ thực tế.
Lúc đầu, tôi đã bối rối về nó. Bởi vì tôi đến từ Python, nơi bạn có thể làm tuple(list(1,2,3))
.
Nó có thiếu ngôn ngữ Scala không? (câu trả lời là - không phải về Scala hay Python, mà là về kiểu tĩnh và kiểu động.)
Đó là lý do tôi cố gắng tìm ra điểm mấu chốt tại sao Scala không thể làm điều này.
Ví dụ mã sau đây triển khai một toTuple
phương thức, có kiểu an toàn toTupleN
và kiểu không an toàn toTuple
.
Các toTuple
phương pháp lấy thông tin loại dài tại thời gian chạy, tức là không có thông tin loại dài tại thời gian biên dịch, vì vậy các kiểu trả về là Product
rất giống như của Python tuple
thực sự (không có loại ở từng vị trí, và không có chiều dài của loại).
Theo cách đó, có thể dẫn đến lỗi thời gian chạy như kiểu không khớp hoặc IndexOutOfBoundException
. (vì vậy list-to-tuple tiện lợi của Python không phải là bữa trưa miễn phí.)
Ngược lại, chính thông tin về độ dài mà người dùng cung cấp sẽ làm cho toTupleN
thời gian biên dịch trở nên an toàn.
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!
bạn có thể làm điều này
bằng cách lặp qua danh sách và áp dụng từng phần tử một.
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
, loại R phải là gì?