Chuyển đổi danh sách Scala thành một bộ giá trị?


88

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?


1
Không thể (ngoại trừ "bằng tay") AFAIK. Cho trước def toTuple(x: List[Int]): R, loại R phải là gì?

7
Nếu nó không cần phải được thực hiện cho một tuple tùy ý kích thước (ví dụ như một phương pháp giả trình bày ở trên), hãy xem xét 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]

2
Trong khi những gì bạn muốn làm là không thể thực hiện được 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:


59

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.


Có một số danh sách kích thước cố định của các phần tử có cùng loại không? Tất nhiên, không nhập các thư viện không chuẩn, như không định hình.

1
@davips không mà tôi biết, nhưng nó đủ dễ dàng để làm của riêng, ví dụ như bạncase class Vec3[A](_1: A, _2: A, _3: A)
Tom Crockett

Đây sẽ là một "bộ" các phần tử có cùng loại, ví dụ: tôi không thể áp dụng bản đồ trên đó.

1
@davips như với bất kỳ dữ liệu mới gõ bạn phải xác định như thế nào mapvv làm việc cho nó
Tom Crockett

1
Loại hạn chế này dường như là một chỉ báo rõ ràng về sự vô dụng của loại an toàn hơn bất cứ thứ gì khác. Nó làm tôi nhớ đến câu trích dẫn "tập hợp trống có nhiều tính chất thú vị."
ely

58

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

4
Trong bối cảnh của giải pháp này, các khiếu nại về an toàn kiểu có nghĩa là bạn có thể nhận được MatchError khi chạy. Nếu muốn, bạn có thể thử bắt lỗi đó và làm gì đó nếu Danh sách có độ dài sai. Một tính năng thú vị khác của giải pháp này là đầu ra không nhất thiết phải là một bộ, nó có thể là một trong các lớp tùy chỉnh của riêng bạn hoặc một số biến đổi của các con số. Tuy nhiên, tôi không nghĩ rằng bạn có thể làm được điều này với những người không có Danh sách (vì vậy bạn có thể cần thực hiện thao tác .toList với đầu vào).
Jim Pivarski

Bạn có thể thực hiện việc này với bất kỳ loại dãy Scala nào bằng cú pháp +:. Ngoài ra, đừng quên thêm một nhận tất cả
ig-dev

48

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 toHListphương thức


18

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)

13

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

10

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ự.


@javadba Tôi đang tìm cách triển khai listToTuple () nhưng cuối cùng không cần. Danh sách cú pháp đường giải quyết vấn đề của tôi.
Peter L

Ngoài ra còn có một cú pháp khác, theo ý kiến ​​của tôi có vẻ tốt hơn một chút:val List(c1, c2, c3) = myList
Kolmar

7

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, xcó 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 xcho 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í.


Loại kết quả của hàm giả thuyết này có phải là Bất kỳ không?

Giới hạn trường hợp lớp học đã được dỡ bỏ trong 2.11 github.com/scala/scala/pull/2305
gdoubleod

5

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)

5

Điều này cũng có thể được thực hiện shapelessvớ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).


Điều này không xuất hiện để làm việc cho các kích cỡ lớn hơn 22, ít nhất là cho shapeless_2.11: 2.3.3
kfkhalili

@kfkhalili Có, và đó không phải là một giới hạn không thể định hình. Bản thân Scala 2 không cho phép các bộ giá trị có hơn 22 phần tử, vì vậy không thể chuyển đổi Danh sách có kích thước lớn hơn thành Tuple bằng bất kỳ phương pháp nào.
Kolmar

4

Sử dụng Khớp mẫu:

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


Khi trả lời một câu hỏi cũ (trên 6 năm) này, bạn nên chỉ ra câu trả lời của bạn khác với tất cả (12) câu trả lời cũ hơn đã được gửi như thế nào. Đặc biệt, câu trả lời của bạn chỉ áp dụng được nếu độ dài của dấu List/ Tuplelà một hằng số đã biết.
jwvh

Cảm ơn bạn @ jwvh, sẽ xem xét ý kiến ​​của bạn trong tương lai.
Michael Olafisoye

2

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 toTuplephương thức, có kiểu an toàn toTupleNvà kiểu không an toàn toTuple.

Các toTuplephươ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à Productrất giống như của Python tuplethự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 toTupleNthờ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!

Trông đẹp - hãy thử ngay bây giờ
StephenBoesch

2

bạn có thể làm điều này

  1. thông qua khớp mẫu (những gì bạn không muốn) hoặc
  2. 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)]
    

1

theo như bạn có loại:

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

def doSomething(a:Int *)

doSomething(x:_*)
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.