Chuyển đổi Danh sách tuple thành bản đồ (và xử lý khóa trùng lặp?)


90

Tôi đã suy nghĩ về một cách hay để chuyển đổi Danh sách bộ mã có khóa trùng lặp [("a","b"),("c","d"),("a","f")]thành bản đồ ("a" -> ["b", "f"], "c" -> ["d"]). Thông thường (trong python), tôi sẽ tạo một bản đồ trống và vòng lặp for qua danh sách và kiểm tra khóa trùng lặp. Nhưng tôi đang tìm kiếm một thứ gì đó có quy mô và giải pháp thông minh hơn ở đây.

btw, loại khóa-giá trị thực tế mà tôi sử dụng ở đây (Int, Node)và tôi muốn chuyển thành bản đồ(Int -> NodeSeq)

Câu trả lời:


78

Nhóm và sau đó chiếu:

scala> val x = List("a" -> "b", "c" -> "d", "a" -> "f")
//x: List[(java.lang.String, java.lang.String)] = List((a,b), (c,d), (a,f))
scala> x.groupBy(_._1).map { case (k,v) => (k,v.map(_._2))}
//res1: scala.collection.immutable.Map[java.lang.String,List[java.lang.String]] = Map(c -> List(d), a -> List(b, f))

Cách nhỏ hơn để sử dụng gấp, theo cách như vậy (bỏ qua map fbước).


124

Đối với nhân viên của Google không mong đợi các bản sao hoặc tốt với chính sách xử lý bản sao mặc định :

List("a" -> 1, "b" -> 2).toMap
// Result: Map(a -> 1, c -> 2)

Kể từ ngày 2.12, chính sách mặc định có nội dung:

Các khóa trùng lặp sẽ bị ghi đè bởi các khóa sau này: nếu đây là một tập hợp không có thứ tự, khóa nào trong bản đồ kết quả là không xác định.


56

Đây là một giải pháp thay thế khác:

x.groupBy(_._1).mapValues(_.map(_._2))

Điều này cho chúng ta một Map[String, SeqView[String,Seq[_]]]... đây là cố ý?
Luigi Plinge

1
@LuigiPlinge A SeqView[String,Seq[_]]cũng là a Seq[String]. Vẫn trong nhận thức muộn màng, tôi không nghĩ rằng điều đó là đáng giá, vì vậy tôi đã loại bỏ view. mapValuesvẫn sẽ thực hiện một chế độ xem trên các giá trị.
Daniel C. Sobral

Điều này đã thực hiện công việc hoàn hảo cho trường hợp của tôi (bài tập về nhà về khóa học): lazy val từ điển curWord)} pair.groupBy ( ._1) .mapValues ​​( .map (_._ 2))}
JasonG

mapValues ​​trả về một chế độ xem bản đồ, không phải là một bản đồ mới scala-lang.org/api/current/index.html#scala.collection.Map
Max Heiber

1
Có thể là muốn x.groupBy(_._1).mapValues(_.map(_._2)).map(identity)mapValuesbiểu thức sẽ được tính lại mỗi khi nó được sử dụng. Xem issue.scala-lang.org/browse/SI-7005
Jeffrey Aguilera,

20

Đối với nhân viên Google quan tâm đến các bản sao:

implicit class Pairs[A, B](p: List[(A, B)]) {
  def toMultiMap: Map[A, List[B]] = p.groupBy(_._1).mapValues(_.map(_._2))
}

> List("a" -> "b", "a" -> "c", "d" -> "e").toMultiMap
> Map("a" -> List("b", "c"), "d" -> List("e")) 

12

Bắt đầu Scala 2.13, hầu hết các bộ sưu tập được cung cấp với phương thức groupMap, phương thức này (như tên gọi của nó cho thấy) một phương thức tương đương (hiệu quả hơn) groupBytheo sau là mapValues:

List("a" -> "b", "c" -> "d", "a" -> "f").groupMap(_._1)(_._2)
// Map[String,List[String]] = Map(a -> List(b, f), c -> List(d))

Điều này:

  • groupcác phần tử s dựa trên phần đầu tiên của bộ giá trị (phần nhóm của Bản đồ nhóm )

  • maps được nhóm các giá trị bằng cách lấy phần thứ hai của chúng (phần bản đồ của Bản đồ nhóm )

Điều này tương đương với list.groupBy(_._1).mapValues(_.map(_._2))nhưng thực hiện trong một lần chuyển qua Danh sách.


4

Đây là một cách thành ngữ khác của Scala để chuyển đổi danh sách các bộ giá trị thành một bản đồ xử lý các khóa trùng lặp. Bạn muốn sử dụng một nếp gấp.

val x = List("a" -> "b", "c" -> "d", "a" -> "f")

x.foldLeft(Map.empty[String, Seq[String]]) { case (acc, (k, v)) =>
  acc.updated(k, acc.getOrElse(k, Seq.empty[String]) ++ Seq(v))
}

res0: scala.collection.immutable.Map[String,Seq[String]] = Map(a -> List(b, f), c -> List(d))

1
Tại sao bạn cho rằng đây là kiểu Scala hơn các giải pháp groupBy-mapValue được cung cấp ở đây?
Make42

@ om-nom-nom tuyên bố "Cách quy mô hơn để sử dụng màn hình gập, theo cách tương tự (bỏ qua bước bản đồ)."
cevaris

Tôi đã hy vọng một đối số hợp lý ;-). Cả om-nom-nom và bài báo được liên kết đều không cung cấp bằng chứng cho câu hỏi của tôi. (Hay tôi đã bỏ lỡ nó?)
Make42

1
@ Make42 Đó là một cách hợp lý hơn để giải quyết vấn đề này, vì tất cả các đơn nguyên đều là đơn nguyên và theo luật là đơn nguyên có thể gập lại. Trong fp, các đối tượng và sự kiện được mô hình hóa dưới dạng monads và không phải tất cả các monads sẽ triển khai groupBy.
soote,

4

Dưới đây bạn có thể tìm thấy một số giải pháp. (GroupBy, FoldLeft, Aggregate, Spark)

val list: List[(String, String)] = List(("a","b"),("c","d"),("a","f"))

GroupBy biến thể

list.groupBy(_._1).map(v => (v._1, v._2.map(_._2)))

Biến thể Fold Left

list.foldLeft[Map[String, List[String]]](Map())((acc, value) => {
  acc.get(value._1).fold(acc ++ Map(value._1 -> List(value._2))){ v =>
    acc ++ Map(value._1 -> (value._2 :: v))
  }
})

Biến thể tổng hợp - Tương tự như gập trái

list.aggregate[Map[String, List[String]]](Map())(
  (acc, value) => acc.get(value._1).fold(acc ++ Map(value._1 -> 
    List(value._2))){ v =>
     acc ++ Map(value._1 -> (value._2 :: v))
  },
  (l, r) => l ++ r
)

Biến thể Spark - Đối với tập dữ liệu lớn (Chuyển đổi sang RDD và sang Bản đồ thuần từ RDD)

import org.apache.spark.rdd._
import org.apache.spark.{SparkContext, SparkConf}

val conf: SparkConf = new 
SparkConf().setAppName("Spark").setMaster("local")
val sc: SparkContext = new SparkContext (conf)

// This gives you a rdd of the same result
val rdd: RDD[(String, List[String])] = sc.parallelize(list).combineByKey(
   (value: String) => List(value),
   (acc: List[String], value) => value :: acc,
   (accLeft: List[String], accRight: List[String]) => accLeft ::: accRight
)

// To convert this RDD back to a Map[(String, List[String])] you can do the following
rdd.collect().toMap

2

Bạn có thể thử cái này

scala> val b = new Array[Int](3)
// b: Array[Int] = Array(0, 0, 0)
scala> val c = b.map(x => (x -> x * 2))
// c: Array[(Int, Int)] = Array((1,2), (2,4), (3,6))
scala> val d = Map(c : _*)
// d: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2, 2 -> 4, 3 -> 6)
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.