Scala cách tốt nhất để biến Bộ sưu tập thành Bản đồ chính?


165

Nếu tôi có một bộ sưu tập cloại Tvà có một thuộc tính ptrên T(loại P, giả sử), cách tốt nhất để thực hiện khóa bản đồ là gì?

val c: Collection[T]
val m: Map[P, T]

Một cách là như sau:

m = new HashMap[P, T]
c foreach { t => m add (t.getP, t) }

Nhưng bây giờ tôi cần một bản đồ có thể thay đổi . Có cách nào tốt hơn để làm điều này sao cho nó thành 1 dòng và tôi kết thúc với một Bản đồ bất biến ? (Rõ ràng tôi có thể biến phần trên thành một tiện ích thư viện đơn giản, như tôi có trong Java, nhưng tôi nghi ngờ rằng trong Scala không có nhu cầu)

Câu trả lời:


232

Bạn có thể dùng

c map (t => t.getP -> t) toMap

nhưng lưu ý rằng điều này cần 2 điểm.


8
Tôi vẫn thích đề xuất của tôi trong trac của a Traversable[K].mapTo( K => V)Traversable[V].mapBy( V => K)đã tốt hơn!
oxbow_lakes

7
Xin lưu ý rằng đây là một hoạt động bậc hai, nhưng điều tương tự cũng xảy ra với hầu hết các biến thể khác được đưa ra ở đây. Nhìn vào mã nguồn của scala.collection.mutable.MapBuilder, v.v., đối với tôi, với mỗi bộ dữ liệu, một bản đồ bất biến mới được tạo ra để thêm bộ dữ liệu.
jcsahnwaldt phục hồi Monica

30
Trên máy của tôi cho một danh sách có 500.000 phần tử, mã Scala này chậm hơn khoảng 20 lần so với cách tiếp cận Java đơn giản (tạo HashMap với kích thước phù hợp, lặp qua danh sách, đưa các phần tử vào bản đồ). Đối với 5.000 yếu tố, Scala ist chậm hơn khoảng 8 lần. Cách tiếp cận vòng lặp được viết bằng Scala nhanh hơn khoảng 3 lần so với biến thể toMap, nhưng vẫn chậm hơn từ 2 đến 7 lần so với Java.
jcsahnwaldt Tái lập lại Monica

8
Bạn có vui lòng cung cấp các nguồn kiểm tra cho cộng đồng SO không? Cám ơn.
dùng573215

8
Thay thế cbằng c.iteratorđể tránh tạo ra bộ sưu tập trung gian.
ghik

21

Bạn có thể xây dựng một Bản đồ với số lượng bộ dữ liệu thay đổi. Vì vậy, sử dụng phương pháp bản đồ trên bộ sưu tập để chuyển đổi nó thành một bộ sưu tập các bộ dữ liệu và sau đó sử dụng mẹo: _ * để chuyển đổi kết quả thành một đối số biến.

scala> val list = List("this", "maps", "string", "to", "length") map {s => (s, s.length)}
list: List[(java.lang.String, Int)] = List((this,4), (maps,4), (string,6), (to,2), (length,6))

scala> val list = List("this", "is", "a", "bunch", "of", "strings")
list: List[java.lang.String] = List(this, is, a, bunch, of, strings)

scala> val string2Length = Map(list map {s => (s, s.length)} : _*)
string2Length: scala.collection.immutable.Map[java.lang.String,Int] = Map(strings -> 7, of -> 2, bunch -> 5, a -> 1, is -> 2, this -> 4)

5
Tôi đã đọc về Scala trong 2 tuần và làm việc qua các ví dụ và chưa một lần tôi thấy ký hiệu ": _ *" này! Cảm ơn rất nhiều vì sự giúp đỡ của bạn
oxbow_lakes 23/03/2016

Chỉ để ghi lại, tôi tự hỏi tại sao chúng ta cần phải chính xác rằng đây là một chuỗi với _ . bản đồ vẫn chuyển đổi trả về một danh sách các tuple ở đây. Vậy tại sao lại là _ ? Ý tôi là nó hoạt động nhưng tôi muốn hiểu kiểu mô tả ở đây
MaatDeamon

1
Đây có phải là hiệu quả hơn so với các phương pháp khác?
Jus12

16

Ngoài giải pháp của @James Iry, bạn cũng có thể thực hiện việc này bằng cách sử dụng một lần. Tôi nghi ngờ rằng giải pháp này nhanh hơn một chút so với phương pháp tuple (tạo ra ít đối tượng rác hơn):

val list = List("this", "maps", "string", "to", "length")
val map = list.foldLeft(Map[String, Int]()) { (m, s) => m(s) = s.length }

Tôi sẽ thử nó (Tôi chắc chắn rằng nó hoạt động :-). Điều gì đang xảy ra với hàm "(m, s) => m (s) = s.length"? Tôi đã thấy ví dụ FoldLeft điển hình với tổng và hàm "_ + _"; Điều này khó hiểu hơn nhiều! Các chức năng dường như giả định rằng tôi đã có một tuple (m, s), mà tôi không thực sự có được
oxbow_lakes

2
Man, Scala đã trở lại kỳ lạ rồi!
missingfaktor

8
@Daniel Tôi thử mã của bạn, nhưng xuất hiện lỗi sau: "cập nhật giá trị không phải là thành viên của scala.collection.immutable.Map [String, Int]". Hãy giải thích mã của bạn làm thế nào để làm việc mã này?
mr.boyfox

1
có vẻ không hoạt động. Đối với tôi, "Ứng dụng không lấy tham số"
jayunit100

7
Phiên bản bất biến : list.foldLeft(Map[String,Int]()) { (m,s) => m + (s -> s.length) }. Lưu ý rằng nếu bạn muốn sử dụng dấu phẩy để xây dựng bộ dữ liệu, bạn cần thêm một cặp dấu ngoặc đơn : ((s, s.length)).
Kelvin

11

Điều này có thể được thực hiện một cách bất biến và với một lần di chuyển bằng cách gấp qua bộ sưu tập như sau.

val map = c.foldLeft(Map[P, T]()) { (m, t) => m + (t.getP -> t) }

Giải pháp hoạt động vì thêm vào Bản đồ bất biến sẽ trả về Bản đồ bất biến mới với mục nhập bổ sung và giá trị này đóng vai trò là bộ tích lũy thông qua thao tác gấp.

Sự đánh đổi ở đây là sự đơn giản của mã so với hiệu quả của nó. Vì vậy, đối với các bộ sưu tập lớn, cách tiếp cận này có thể phù hợp hơn so với sử dụng 2 triển khai truyền tải như áp dụng maptoMap.


8

Một giải pháp khác (có thể không hoạt động cho tất cả các loại)

import scala.collection.breakOut
val m:Map[P, T] = c.map(t => (t.getP, t))(breakOut)

điều này tránh việc tạo danh sách trung gian, thông tin thêm ở đây: Scala 2.8 breakOut


7

Những gì bạn đang cố gắng để đạt được là một chút không xác định.
Điều gì nếu hai hoặc nhiều mục trong ccùng một p? Mục nào sẽ được ánh xạ tới đó ptrong bản đồ?

Cách chính xác hơn để xem xét điều này là thu được một bản đồ giữa pvà tất cả ccác mục có nó:

val m: Map[P, Collection[T]]

Điều này có thể dễ dàng đạt được với groupBy :

val m: Map[P, Collection[T]] = c.groupBy(t => t.p)

Nếu bạn vẫn muốn bản đồ gốc, ví dụ, bạn có thể ánh xạ ptới bản đồ đầu tiên tcó bản đồ đó:

val m: Map[P, T] = c.groupBy(t => t.p) map { case (p, ts) =>  p -> ts.head }

1
Một điều chỉnh tiện dụng về điều này là sử dụng collectthay vì map. Vd : c.group(t => t.p) collect { case (Some(p), ts) => p -> ts.head }. Bằng cách này, bạn có thể thực hiện những việc như làm phẳng bản đồ khi khóa là Tùy chọn [_].
healsjnr

@healsjnr Chắc chắn, điều này có thể được nói cho bất kỳ bản đồ. Nó không phải là vấn đề cốt lõi ở đây, mặc dù.
Eyal Roth

1
Bạn có thể sử dụng .mapValues(_.head)thay vì bản đồ.
lex82

2

Đây có lẽ không phải là cách hiệu quả nhất để biến danh sách thành bản đồ, nhưng nó làm cho mã cuộc gọi dễ đọc hơn. Tôi đã sử dụng chuyển đổi ngầm để thêm phương thức mapBy vào Danh sách:

implicit def list2ListWithMapBy[T](list: List[T]): ListWithMapBy[T] = {
  new ListWithMapBy(list)
}

class ListWithMapBy[V](list: List[V]){
  def mapBy[K](keyFunc: V => K) = {
    list.map(a => keyFunc(a) -> a).toMap
  }
}

Ví dụ mã gọi:

val list = List("A", "AA", "AAA")
list.mapBy(_.length)                  //Map(1 -> A, 2 -> AA, 3 -> AAA)

Lưu ý rằng do chuyển đổi ngầm định, mã người gọi cần nhập ẩn ẩn của Scala.


2
c map (_.getP) zip c

Hoạt động tốt và rất trực quan


8
Vui lòng thêm chi tiết.
Syeda Zunaira

2
Tôi xin lỗi. Nhưng, đây là câu trả lời cho câu hỏi "Scala cách tốt nhất để biến Bộ sưu tập thành Bản đồ chính?" như Ben Lings là.
Jörg Bächtiger

1
Và Ben không cung cấp bất kỳ lời giải thích?
shinzou

1
điều này tạo ra hai danh sách và kết hợp thành một "bản đồ" bằng cách sử dụng các phần tử cdưới dạng khóa (sắp xếp). Lưu ý "bản đồ" vì bộ sưu tập kết quả không phải là scala Mapnhưng tạo ra một danh sách khác / lặp lại các bộ dữ liệu ... nhưng hiệu quả là như nhau đối với mục đích của OP, tôi sẽ không giảm giá đơn giản nhưng nó không hiệu quả như foldLeftgiải pháp, cũng không phải là giải pháp câu trả lời thực sự cho câu hỏi "chuyển đổi thành bộ sưu tập thành bản đồ chính"
Dexter Legaspi

2

Làm thế nào về việc sử dụng zip và toMap?

myList.zip(myList.map(_.length)).toMap

1

Đối với những gì nó có giá trị, đây là hai cách làm vô nghĩa :

scala> case class Foo(bar: Int)
defined class Foo

scala> import scalaz._, Scalaz._
import scalaz._
import Scalaz._

scala> val c = Vector(Foo(9), Foo(11))
c: scala.collection.immutable.Vector[Foo] = Vector(Foo(9), Foo(11))

scala> c.map(((_: Foo).bar) &&& identity).toMap
res30: scala.collection.immutable.Map[Int,Foo] = Map(9 -> Foo(9), 11 -> Foo(11))

scala> c.map(((_: Foo).bar) >>= (Pair.apply[Int, Foo] _).curried).toMap
res31: scala.collection.immutable.Map[Int,Foo] = Map(9 -> Foo(9), 11 -> Foo(11))

Ngoài ra, fwiw, đây là cách hai người đó sẽ nhìn trong Haskell: Map.fromList $ map (bar &&& id) c, Map.fromList $ map (bar >>= (,)) c.
missingfaktor

-1

Điều này làm việc cho tôi:

val personsMap = persons.foldLeft(scala.collection.mutable.Map[Int, PersonDTO]()) {
    (m, p) => m(p.id) = p; m
}

Bản đồ phải có thể thay đổi và Bản đồ phải được trả lại do việc thêm vào Bản đồ có thể thay đổi sẽ không trả lại bản đồ.


1
Trên thực tế, nó có thể được triển khai một cách bất biến như sau: val personsMap = persons.foldLeft(Map[Int, PersonDTO]()) { (m, p) => m + (p.id -> p) }Bản đồ có thể là bất biến, như đã được chứng minh ở trên, bởi vì thêm vào Bản đồ bất biến sẽ trả về một Bản đồ bất biến mới với mục nhập bổ sung. Giá trị này đóng vai trò là bộ tích lũy thông qua thao tác gấp.
RamV13

-2

sử dụng map () trên bộ sưu tập theo sau với toMap

val map = list.map(e => (e, e.length)).toMap

3
Làm thế nào khác với câu trả lời đã được gửi, và được chấp nhận, 7 năm trước?
jwvh

-4

Nếu chuyển đổi từ Chuỗi Json (đọc tệp json) sang Scala Map

import spray.json._
import DefaultJsonProtocol._

val jsonStr = Source.fromFile(jsonFilePath).mkString
val jsonDoc=jsonStr.parseJson
val map_doc=jsonDoc.convertTo[Map[String, JsValue]]

// Get a Map key value
val key_value=map_doc.get("key").get.convertTo[String]

// If nested json, re-map it.
val key_map=map_doc.get("nested_key").get.convertTo[Map[String, JsValue]]
println("Nested Value " + key_map.get("key").get)

Câu hỏi 11 tuổi này không hỏi gì về JSON. Câu trả lời của bạn là không đúng chỗ.
jwvh
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.