Cách thanh lịch để đảo bản đồ trong Scala


97

Học Scala hiện tại và cần thiết để đảo ngược Bản đồ để thực hiện một số tra cứu khóa-> giá trị đảo ngược. Tôi đã tìm kiếm một cách đơn giản để làm điều này, nhưng chỉ nghĩ ra:

(Map() ++ origMap.map(kvp=>(kvp._2->kvp._1)))

Có ai có cách tiếp cận thanh lịch hơn không?

Câu trả lời:


174

Giả sử các giá trị là duy nhất, điều này hoạt động:

(Map() ++ origMap.map(_.swap))

Tuy nhiên, trên Scala 2.8, nó dễ dàng hơn:

origMap.map(_.swap)

Có thể làm được điều đó là một phần lý do tại sao Scala 2.8 có một thư viện bộ sưu tập mới.


1
Cẩn thận! Bạn có thể mất các giá trị với giải pháp này. Ví dụ Map(1 -> "A", 2 -> "B", 3 -> "B").map(_.swap)kết quả làMap(A -> 1, B -> 3)
dev-null

1
@ dev-null Tôi xin lỗi, nhưng ví dụ của bạn không nằm trong giả định bắt buộc.
Daniel C. Sobral

Tôi đồng ý. Xin lỗi, tôi đã bỏ qua điều đó.
dev-null

45

Về mặt toán học, ánh xạ có thể không thể đảo ngược (bị tổn thương), ví dụ, từ Map[A,B], bạn không thể nhận được Map[B,A], nhưng đúng hơn là bạn nhận được Map[B,Set[A]], bởi vì có thể có các khóa khác nhau được liên kết với các giá trị giống nhau. Vì vậy, nếu bạn muốn biết tất cả các khóa, đây là mã:

scala> val m = Map(1 -> "a", 2 -> "b", 4 -> "b")
scala> m.groupBy(_._2).mapValues(_.keys)
res0: Map[String,Iterable[Int]] = Map(b -> Set(2, 4), a -> Set(1))

2
.map(_._1)sẽ nhanh hơn giống như chỉ.keys
cheezsteak

Bây giờ nhờ bạn, thậm chí một vài ký tự ngắn hơn. Sự khác biệt bây giờ là bạn nhận được Sets thay vì Lists như trước đây.
Rok Kralj

1
Hãy cẩn thận khi sử dụng .mapValuesvì nó trả về một dạng xem. Đôi khi, đây là những gì bạn muốn, nhưng nếu bạn không cẩn thận, nó có thể tiêu tốn rất nhiều bộ nhớ và CPU. Để buộc nó vào bản đồ, bạn có thể làm m.groupBy(_._2).mapVaues(_.keys).map(identity), hoặc bạn có thể thay thế lệnh gọi tới .mapValues(_.keys)bằng .map { case (k, v) => k -> v.keys }.
Mark T.

10

Bạn có thể tránh nội dung ._1 trong khi lặp lại theo một số cách.

Đây là một cách. Điều này sử dụng một phần chức năng bao gồm một trường hợp duy nhất quan trọng đối với bản đồ:

Map() ++ (origMap map {case (k,v) => (v,k)})

Đây là một cách khác:

import Function.tupled        
Map() ++ (origMap map tupled {(k,v) => (v,k)})

Phép lặp bản đồ gọi một hàm có bộ giá trị hai phần tử và hàm ẩn danh muốn có hai tham số. Function.tupled thực hiện bản dịch.


6

Tôi đến đây để tìm cách đảo bản đồ loại Bản đồ [A, Seq [B]] sang Bản đồ [B, Seq [A]], trong đó mỗi B trong bản đồ mới được liên kết với mỗi A trong bản đồ cũ cho mà B được chứa trong chuỗi liên kết của A.

Ví dụ:
Map(1 -> Seq("a", "b"), 2-> Seq("b", "c"))
sẽ đảo ngược với
Map("a" -> Seq(1), "b" -> Seq(1, 2), "c" -> Seq(2))

Đây là giải pháp của tôi:

val newMap = oldMap.foldLeft(Map[B, Seq[A]]().withDefaultValue(Seq())) {
  case (m, (a, bs)) => bs.foldLeft(m)((map, b) => map.updated(b, m(b) :+ a))
}

trong đó Bản đồ cũ thuộc loại Map[A, Seq[B]]và Bản đồ mới thuộc loạiMap[B, Seq[A]]

Các foldLefts lồng vào nhau khiến tôi hơi co rúm lại một chút, nhưng đây là cách đơn giản nhất mà tôi có thể tìm thấy để thực hiện kiểu đảo ngược này. Bất cứ ai có một giải pháp sạch hơn?


giải pháp rất tốt đẹp! @Rok, giải pháp của ông là bằng cách nào đó khác hơn của bạn một chút tôi nghĩ rằng vì anh ta biến đổi: Map[A, Seq[B]]để Map[B, Seq[A]]nơi trasnforms giải pháp của bạn Map[A, Seq[B]]để Map[Seq[B], Seq[A]].
YH

1
Trong trường hợp đó, nếu không có hai nếp gấp lồng nhau và có thể performant hơn:a.toSeq.flatMap { case (a, b) => b.map(_ -> a) }.groupBy(_._2).mapValues(_.map(_._1))
Rok Kralj

6

OK, vì vậy đây là một câu hỏi rất cũ với nhiều câu trả lời hay, nhưng tôi đã tạo ra một con dao tối tân, hoàn hảo nhất, Swiss-Army-Dao, Mapbiến tần và đây là nơi để đăng nó.

Nó thực sự là hai biến tần. Một cho các yếu tố giá trị riêng lẻ ...

//from Map[K,V] to Map[V,Set[K]], traverse the input only once
implicit class MapInverterA[K,V](m :Map[K,V]) {
  def invert :Map[V,Set[K]] =
    m.foldLeft(Map.empty[V, Set[K]]) {
      case (acc,(k, v)) => acc + (v -> (acc.getOrElse(v,Set()) + k))
    }
}

... và một cái khác, khá tương tự, cho các bộ sưu tập giá trị.

import scala.collection.generic.CanBuildFrom
import scala.collection.mutable.Builder
import scala.language.higherKinds

//from Map[K,C[V]] to Map[V,C[K]], traverse the input only once
implicit class MapInverterB[K,V,C[_]](m :Map[K,C[V]]
                                     )(implicit ev :C[V] => TraversableOnce[V]) {
  def invert(implicit bf :CanBuildFrom[Nothing,K,C[K]]) :Map[V,C[K]] =
    m.foldLeft(Map.empty[V, Builder[K,C[K]]]) {
      case (acc, (k, vs)) =>
        vs.foldLeft(acc) {
          case (a, v) => a + (v -> (a.getOrElse(v,bf()) += k))
        }
    }.mapValues(_.result())
}

sử dụng:

Map(2 -> Array('g','h'), 5 -> Array('g','y')).invert
//res0: Map(g -> Array(2, 5), h -> Array(2), y -> Array(5))

Map('q' -> 1.1F, 'b' -> 2.1F, 'c' -> 1.1F, 'g' -> 3F).invert
//res1: Map(1.1 -> Set(q, c), 2.1 -> Set(b), 3.0 -> Set(g))

Map(9 -> "this", 8 -> "that", 3 -> "thus", 2 -> "thus").invert
//res2: Map(this -> Set(9), that -> Set(8), thus -> Set(3, 2))

Map(1L -> Iterator(3,2), 5L -> Iterator(7,8,3)).invert
//res3: Map(3 -> Iterator(1, 5), 2 -> Iterator(1), 7 -> Iterator(5), 8 -> Iterator(5))

Map.empty[Unit,Boolean].invert
//res4: Map[Boolean,Set[Unit]] = Map()

Tôi muốn có cả hai phương thức trong cùng một lớp ngầm định nhưng tôi càng dành nhiều thời gian xem xét nó thì nó càng xuất hiện nhiều vấn đề hơn.


3

Bạn có thể đảo ngược bản đồ bằng cách sử dụng:

val i = origMap.map({case(k, v) => v -> k})

Vấn đề với cách tiếp cận này là nếu các giá trị của bạn, hiện đã trở thành khóa băm trong bản đồ của bạn, không phải là duy nhất, bạn sẽ bỏ các giá trị trùng lặp. Để minh họa:

scala> val m = Map("a" -> 1, "b" -> 2, "c" -> 3, "d" -> 1)
m: scala.collection.immutable.Map[String,Int] = Map(a -> 1, b -> 2, c -> 3, d -> 1)

// Notice that 1 -> a is not in our inverted map
scala> val i = m.map({ case(k , v) => v -> k})
i: scala.collection.immutable.Map[Int,String] = Map(1 -> d, 2 -> b, 3 -> c)

Để tránh điều này, trước tiên, bạn có thể chuyển đổi bản đồ của mình thành một danh sách các bộ giá trị, sau đó đảo ngược để không làm mất bất kỳ giá trị trùng lặp nào:

scala> val i = m.toList.map({ case(k , v) => v -> k})
i: List[(Int, String)] = List((1,a), (2,b), (3,c), (1,d))

1

Trong scala REPL:

scala> val m = Map(1 -> "one", 2 -> "two")
m: scala.collection.immutable.Map[Int,java.lang.String] = Map(1 -> one, 2 -> two)

scala> val reversedM = m map { case (k, v) => (v, k) }
reversedM: scala.collection.immutable.Map[java.lang.String,Int] = Map(one -> 1, two -> 2)

Lưu ý rằng các giá trị trùng lặp sẽ bị ghi đè bởi lần bổ sung cuối cùng vào bản đồ:

scala> val m = Map(1 -> "one", 2 -> "two", 3 -> "one")
m: scala.collection.immutable.Map[Int,java.lang.String] = Map(1 -> one, 2 -> two, 3 -> one)

scala> val reversedM = m map { case (k, v) => (v, k) }
reversedM: scala.collection.immutable.Map[java.lang.String,Int] = Map(one -> 3, two -> 2)

1

Bắt đầu Scala 2.13, để hoán đổi khóa / giá trị mà không làm mất các khóa được liên kết với các giá trị giống nhau, chúng ta có thể sử dụng phương thức groupMapMap mới , phương thức này (như tên gọi của nó cho thấy) tương đương với a và ping trên các mục được nhóm.groupBymap

Map(1 -> "a", 2 -> "b", 4 -> "b").groupMap(_._2)(_._1)
// Map("b" -> List(2, 4), "a" -> List(1))

Điều này:

  • groups phần tử dựa trên tuple part ( _._2) (phần nhóm của nhóm Map)

  • mapCác mục được nhóm lại bằng cách lấy phần đầu tiên của chúng ( _._1) (phần bản đồ của Bản đồ nhóm )

Đây có thể được coi là phiên bản một lần của map.groupBy(_._2).mapValues(_.map(_._1)).


Thật tệ là nó sẽ không biến Map[K, C[V]]thành Map[V, C[K]].
jwvh 14/02/19

0
  1. Inverse là một cái tên hay hơn cho phép toán này hơn là đảo ngược (như trong "nghịch đảo của một hàm toán học")

  2. Tôi thường thực hiện phép biến đổi nghịch đảo này không chỉ trên bản đồ mà còn trên các bộ sưu tập khác (bao gồm cả Seq). Tôi thấy tốt nhất là không nên giới hạn định nghĩa của phép toán nghịch đảo của tôi đối với các bản đồ một-một. Đây là định nghĩa tôi sử dụng cho bản đồ (vui lòng đề xuất các cải tiến cho việc triển khai của tôi).

    def invertMap[A,B]( m: Map[A,B] ) : Map[B,List[A]] = {
      val k = ( ( m values ) toList ) distinct
      val v = k map { e => ( ( m keys ) toList ) filter { x => m(x) == e } }
      ( k zip v ) toMap
    }

Nếu đó là bản đồ một đối một, bạn sẽ có các danh sách đơn lẻ có thể được thử nghiệm và chuyển đổi thành Bản đồ [B, A] thay vì Bản đồ [B, Danh sách [A]].


0

Chúng tôi có thể thử sử dụng foldLeftchức năng này để xử lý các va chạm và đảo ngược bản đồ trong một lần di chuyển.

scala> def invertMap[A, B](inputMap: Map[A, B]): Map[B, List[A]] = {
     |     inputMap.foldLeft(Map[B, List[A]]()) {
     |       case (mapAccumulator, (value, key)) =>
     |         if (mapAccumulator.contains(key)) {
     |           mapAccumulator.updated(key, mapAccumulator(key) :+ value)
     |         } else {
     |           mapAccumulator.updated(key, List(value))
     |         }
     |     }
     |   }
invertMap: [A, B](inputMap: Map[A,B])Map[B,List[A]]

scala> val map = Map(1 -> 2, 2 -> 2, 3 -> 3, 4 -> 3, 5 -> 5)
map: scala.collection.immutable.Map[Int,Int] = Map(5 -> 5, 1 -> 2, 2 -> 2, 3 -> 3, 4 -> 3)

scala> invertMap(map)
res0: Map[Int,List[Int]] = Map(5 -> List(5), 2 -> List(1, 2), 3 -> List(3, 4))

scala> val map = Map("A" -> "A", "B" -> "A", "C" -> "C", "D" -> "C", "E" -> "E")
map: scala.collection.immutable.Map[String,String] = Map(E -> E, A -> A, B -> A, C -> C, D -> C)

scala> invertMap(map)
res1: Map[String,List[String]] = Map(E -> List(E), A -> List(A, B), C -> List(C, D))
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.