Елегантний спосіб перевернути карту в Скалі


97

Навчання Scala в даний час і потрібно, щоб перетворити карту, щоб зробити кілька перевернутих значень-> пошук ключів. Я шукав простий спосіб зробити це, але придумав лише:

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

Хтось має більш елегантний підхід?

Відповіді:


174

Якщо припустити, що значення унікальні, це працює:

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

Однак у Scala 2.8 це простіше:

origMap.map(_.swap)

Можливість зробити це є частиною причини, чому у Scala 2.8 є нова бібліотека колекцій.


1
Обережно! Ви можете втратити значення за допомогою цього рішення. Наприклад Map(1 -> "A", 2 -> "B", 3 -> "B").map(_.swap)результати вMap(A -> 1, B -> 3)
dev-null

1
@ dev-null Вибачте, але ваш приклад не підпадає під необхідне припущення.
Даніель К. Собрал

Я згоден. Вибачте, я це не помітив.
dev-null

45

Математично відображення може бути незворотним (ін'єктивним), наприклад, з Map[A,B], ви не можете отримати Map[B,A], а скоріше ви отримаєте Map[B,Set[A]], тому що можуть бути різні ключі, пов'язані з однаковими значеннями. Отже, якщо вам цікаво знати всі ключі, ось код:

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)було б більш розбірливим як раз.keys
чизк

Тепер завдяки вам навіть на кілька символів коротше. Зараз різниця полягає в тому, що ви отримуєте Sets замість Lists, як раніше.
Rok Kralj

1
Будьте обережні при використанні, .mapValuesоскільки це повертає вигляд. Іноді, це те, що ви хочете, але якщо ви не будете обережні, це може зайняти багато пам'яті та процесора. Для того, щоб змусити його в карту, ви можете зробити m.groupBy(_._2).mapVaues(_.keys).map(identity), або ви могли б замінити виклик .mapValues(_.keys)з .map { case (k, v) => k -> v.keys }.
Марк Т.

10

Ви можете уникнути ._1 матеріалів під час повторення кількома способами.

Ось один із способів. Для цього використовується часткова функція, яка охоплює єдиний випадок, який має значення для карти:

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

Ось ще один спосіб:

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

Ітерація карти викликає функцію з двома елементами, а анонімна функція хоче двох параметрів. Функція.tupled робить переклад.


6

Я прийшов сюди, шукаючи спосіб перетворити карту типу Map [A, Seq [B]] на Map [B, Seq [A]], де кожен B на новій карті асоціюється з кожним A на старій карті для який B містився в асоційованій послідовності А.

Наприклад,
Map(1 -> Seq("a", "b"), 2-> Seq("b", "c"))
буде інвертувати до
Map("a" -> Seq(1), "b" -> Seq(1, 2), "c" -> Seq(2))

Ось моє рішення:

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

де oldMap має тип, Map[A, Seq[B]]а newMap - типMap[B, Seq[A]]

Вкладені складчасті ліфти змушують мене трохи скупитися, але це найпростіший спосіб, який я міг би знайти для здійснення цього типу інверсії. У кого є чистіший розчин?


дуже приємне рішення! @Rok, його рішення як - то інакше , ніж ваш трохи , я думаю , тому що він трансформує: Map[A, Seq[B]]до Map[B, Seq[A]]де ваші trasnforms рішення Map[A, Seq[B]]для Map[Seq[B], Seq[A]].
YH

1
У такому випадку без двох вкладених складок та можливого більшого виконання:a.toSeq.flatMap { case (a, b) => b.map(_ -> a) }.groupBy(_._2).mapValues(_.map(_._1))
Rok Kralj

6

Гаразд, тож це дуже старе питання з багатьма хорошими відповідями, але я створив остаточний, Mapінвертор , швейцарсько-армійський ніж, інвертор, і це місце для його розміщення.

Це фактично два інвертора. Один для окремих елементів значення ...

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

... та ще одна, досить схожа, для колекцій цінностей.

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

використання:

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

Я вважаю за краще, щоб обидва методи були в тому ж неявному класі, але чим більше часу я витрачав на його розгляд, тим більш проблематичним виявилося.


3

Ви можете інвертувати карту, використовуючи:

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

Проблема такого підходу полягає в тому, що якщо ваші значення, які стали хеш-ключами на вашій карті, не є унікальними, ви скинете повторювані значення. Проілюструвати:

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)

Щоб уникнути цього, ви можете перетворити свою карту в список кортежів, а потім інвертувати, щоб не скидати жодних повторюваних значень:

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

В масштабі 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)

Зауважте, що повторювані значення будуть замінені останнім доповненням до карти:

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

Починаючи Scala 2.13, для того, щоб поміняти ключ / значення без втрати ключів, пов'язаних з тими самими значеннями, ми можемо використовувати Mapновий метод groupMap , який (як випливає з назви) є еквівалентом а groupByта mapпінг над згрупованими елементами.

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

Це:

  • groups елементів на основі їх другої кортежної частини ( _._2) (групова частина групової карти)

  • maps групує елементи, беручи їхню першу кортежну частину ( _._1) (карта частини групи Map )

Це можна розглядати як варіант однопрохідної з map.groupBy(_._2).mapValues(_.map(_._1)).


Шкода, що він не перетвориться Map[K, C[V]]на Map[V, C[K]].
jwvh

0
  1. Inverse - краща назва цієї операції, ніж зворотний (як у "оберненій математичній функції")

  2. Я часто роблю це обернене перетворення не тільки на картах, але й на інших (включаючи Seq) колекціях. Я вважаю, що найкраще не обмежувати визначення моєї зворотної операції картами один на один. Ось визначення, з яким я працюю для карт (будь ласка, підкажіть покращення моєї реалізації).

    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
    }

Якщо це карта "один на один", ви отримуєте списки однотонних, які можна тривіально перевірити і перетворити на карту [B, A], а не Map [B, List [A]].


0

Ми можемо спробувати скористатися цією foldLeftфункцією, яка піклується про зіткнення та інвертувати карту в один проїзд.

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))
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.