Элегантный способ инвертировать карту в Scala

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

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

У кого-нибудь более элегантный подход?

Ответ 1

Предполагая, что значения уникальны, это работает:

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

В Scala 2.8, однако, это проще:

origMap.map(_.swap)

Возможность сделать это является частью причины, по которой Scala 2.8 имеет новую библиотеку коллекций.

Ответ 2

Математически отображение может быть необратимым (инъективным), например, из 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))

Ответ 3

Вы можете избежать использования._1 при повторении нескольких способов.

Вот один из способов. Это использует частичную функцию, которая охватывает один и только случай, который имеет значение для карты:

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

Здесь другой способ:

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

Итерация карты вызывает функцию с двухэлементным кортежем, а анонимная функция хочет два параметра. Function.tupled делает перевод.

Ответ 4

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

например.,
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]]

Вложенные foldLefts заставляют меня съеживаться немного, но это самый простой способ найти такой тип инверсии. У кого-нибудь есть более чистое решение?

Ответ 5

Вы можете инвертировать карту, используя:

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

Ответ 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()

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

Ответ 7

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

Обратите внимание, что дублирующиеся значения будут перезаписаны последним дополнением к карте:

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)

Ответ 8

Начиная с Scala 2.13, чтобы поменять местами ключи/значения без потери ключей, связанных с теми же значениями, мы можем использовать метод Map new K)(f:A=>B):scala.collection.immutable.Map[K,CC[B]] rel="nofollow noreferrer">groupMap, который (как следует из его названия) является эквивалентом groupBy и ping map сгруппированных элементов.

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

Это:

  • group элементы на основе их второй части кортежа (_._2) (групповая часть карты группы)

  • map сгруппированные элементы, взяв их первую часть кортежа (_._1) (часть карты группы Map)

Это можно рассматривать как однопроходную версию map.groupBy(_._2).mapValues(_.map(_._1)).

Ответ 9

  • Обратное - это лучшее имя для этой операции, чем обратное (как в "обратном математической функции" )

  • Я часто делаю это обратное преобразование не только на картах, но и на других (включая 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], а не карту [B, список [A]].

Ответ 10

Мы можем попробовать использовать эту функцию 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))