Элегантный способ инвертировать карту в Scala
изучение Scala в настоящее время и необходимо инвертировать карту, чтобы сделать некоторые инвертированные значения->ключевые поиски. Я ищу простой способ сделать это, но придумал только:
(Map() ++ origMap.map(kvp=>(kvp._2->kvp._1)))
у кого-нибудь есть более элегантный подход?
9 ответов:
предполагая, что значения уникальны, это работает:
(Map() ++ origMap.map(_.swap))
на Scala 2.8, однако, проще:
origMap.map(_.swap)
возможность сделать это является частью причины, по которой Scala 2.8 имеет новую библиотеку коллекций.
математически отображение не может быть обратимым (инъективным), например, от
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))
Вы можете избежать ._1 материал при повторении несколькими способами.
вот один из способов. Это использует частичную функцию, которая охватывает один и только случай, который имеет значение для карты:
Map() ++ (origMap map {case (k,v) => (v,k)})
вот еще один способ:
import Function.tupled Map() ++ (origMap map tupled {(k,v) => (v,k)})
итерация map вызывает функцию с двухэлементным кортежем, и анонимная функция хочет два параметра. Функция.кортеж делает перевод.
Я пришел сюда, чтобы найти способ инвертировать карту типа Map[A, Seq[B]] на карту[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]]
вложенные складки делают меня коробит немного, но это самый простой способ я нашел для этого типа инверсии. У кого-нибудь есть более чистое решение?
вы можете инвертировать карту, используя:
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))
хорошо, так что это очень старый вопрос со многими хорошими ответами, но я построил окончательный, быть-все-и-конец-все, швейцарский армейский нож,
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()
Я бы предпочел иметь оба метода в одном неявном классе, но тем больше времени я потраченные на его изучение, тем более проблематичным он оказался.
в 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)
Inverse-лучшее название для этой операции, чем reverse (как в "обратном математической функции")
Я часто делаю это обратное преобразование не только на картах, но и на других (включая 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, List[A]].
мы можем попробовать использовать это
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))