Câu trả lời:
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.
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))
.map(_._1)
sẽ nhanh hơn giống như chỉ.keys
Set
s thay vì List
s như trước đây.
.mapValues
vì 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 }
.
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.
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?
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]]
.
a.toSeq.flatMap { case (a, b) => b.map(_ -> a) }.groupBy(_._2).mapValues(_.map(_._1))
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, Map
biế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.
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))
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)
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.groupBy
map
Map(1 -> "a", 2 -> "b", 4 -> "b").groupMap(_._2)(_._1)
// Map("b" -> List(2, 4), "a" -> List(1))
Điều này:
group
s phần tử dựa trên tuple part ( _._2
) (phần nhóm của nhóm Map)
map
Cá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))
.
Map[K, C[V]]
thành Map[V, C[K]]
.
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")
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]].
Chúng tôi có thể thử sử dụng foldLeft
chứ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))
Map(1 -> "A", 2 -> "B", 3 -> "B").map(_.swap)
kết quả làMap(A -> 1, B -> 3)