Scalaでマップを反転させるエレガントな方法


97

現在、Scalaを学習しており、Mapを反転して値を逆にしてキーを検索する必要があります。私はこれを行う簡単な方法を探していましたが、思いついたのは次のものだけです。

(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申し訳ありませんが、あなたの例は必要な仮定に該当しません。
ダニエルC.ソブラル

同意する。すみません、見落としました。
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
cheezsteak

さて、あなたのおかげで、数文字でも短くなりました。現在の違いは、以前のようにSetsではなくs を取得することですList
Rok Kralj、2016

1
.mapValuesビューを返すので注意してください。これが必要な場合もありますが、注意しないと大量のメモリとCPUを消費する可能性があります。マップにそれを強制するために、あなたが行うことができますm.groupBy(_._2).mapVaues(_.keys).map(identity)、またはあなたはへの呼び出しを置き換えることができる.mapValues(_.keys).map { case (k, v) => k -> v.keys }
マーク

10

いくつかの方法で繰り返しながら、._ 1のものを回避できます。

これが1つの方法です。これは、マップに関係する唯一のケースをカバーする部分関数を使用します。

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

ここに別の方法があります:

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

マップの反復は2つの要素のタプルを使用して関数を呼び出し、無名関数は2つのパラメーターを必要とします。Function.tupledは翻訳を行います。


6

タイプ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を使用すると、少し不規則になりますが、これは、このタイプの反転を実現するために見つけた最も簡単な方法です。誰もがよりクリーンな解決策を持っていますか?


とても良い解決策です!:@Rok、彼の解決策は、彼が変形するので、私は考えて少しあなたより何とか違うMap[A, Seq[B]]Map[B, Seq[A]]どこソリューションtrasnforms Map[A, Seq[B]]Map[Seq[B], Seq[A]]
YH、

1
その場合には、ネストされた2つの折り目と可能なよりパフォーマンスなし:a.toSeq.flatMap { case (a, b) => b.map(_ -> a) }.groupBy(_._2).mapValues(_.map(_._1))
ロクKralj

6

わかりました。これは非常に古い質問であり、多くの良い答えがありますが、私は究極の、万能のスイスアーミーナイフMapインバーターを構築しました。ここに投稿します。

それは実際には2つのインバータです。個々の値の要素に1つ...

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

...そしてもう1つは、非常によく似た値のコレクションです。

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

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)

1

出発Scala 2.13スワップキーに順番に、/同じ値に関連付けられたキーを失うことなく、値は、我々が使用できるMapGROUPMAPの(その名前が示唆するように)と同等である方法、groupByおよびmapグループ化されたアイテム上のpingを。

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

この:

  • group2番目のタプル部分(_._2)に基づくs要素(グループマップのグループ部分)

  • map彼らの最初のタプルの部分を取ることによって、グループ化された商品(S _._1)(グループのマップパーツマップに

これは、のワンパスバージョン見なすことができますmap.groupBy(_._2).mapValues(_.map(_._1))


残念ながら、に変換Map[K, C[V]]されませんMap[V, C[K]]
jwvh

0
  1. 逆は、「数学関数の逆」のように、逆よりもこの操作に適した名前です。

  2. 私はよくこの逆変換をマップだけでなく他の(Seqを含む)コレクションでも行います。私の逆演算の定義を1対1のマップに限定しないのが最善です。これが私がマップに対して操作する定義です(実装の改善を提案してください)。

    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
    }

1対1のマップの場合、簡単にテストしてMap [B、List [A]]ではなくMap [B、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))
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.