Scala에서지도를 반전시키는 우아한 방법


97

현재 스칼라를 배우고 있으며 반전 된 값-> 키 조회를 수행하기 위해 맵을 반전해야합니다. 이 작업을 수행하는 간단한 방법을 찾고 있었지만 다음과 같은 방법으로 만 생각했습니다.

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

1
@ dev-null 죄송하지만 귀하의 예는 필수 가정에 해당하지 않습니다.
Daniel C. Sobral

나는 동의한다. 죄송합니다. 간과했습니다.
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)등의 단지 legibile 것.keys
cheezsteak

이제 당신 덕분에 몇 글자 더 짧아졌습니다. 이제 차이점은 이전과 같이 Sets 대신 s 를 얻는 것입니다 List.
Rok Kralj

1
.mapValues뷰를 반환하므로 사용시주의하십시오 . 때로는 이것이 원하는 것이지만주의하지 않으면 많은 메모리와 CPU를 소비 할 수 있습니다. 지도에 강제로 삽입하려면을 수행 m.groupBy(_._2).mapVaues(_.keys).map(identity)하거나 호출을 .mapValues(_.keys)로 바꿀 수 .map { case (k, v) => k -> v.keys }있습니다.
Mark T.

10

몇 가지 방법으로 반복하는 동안 ._1 항목을 피할 수 있습니다.

한 가지 방법이 있습니다. 이것은지도에 중요한 유일한 경우를 다루는 부분 함수를 사용합니다.

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

다른 방법이 있습니다.

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

맵 반복은 두 개의 요소 튜플이있는 함수를 호출하고 익명 함수는 두 개의 매개 변수를 원합니다. Function.tupled가 번역을합니다.


6

Map [A, Seq [B]] 유형의 Map을 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, 그의 해결책은 당신 A는 그가 변환하기 때문에 내가 생각 비트보다 어떻게 든 다른 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 2015-08-20

6

좋아요,이 질문은 좋은 답변이 많은 아주 오래된 질문입니다.하지만 저는 궁극의 완전하고 끝없는 Swiss-Army-knife, 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 메서드를 사용할 수 있습니다.이 메서드는 이름에서 알 수 있듯이 a groupBymap그룹화 된 항목에 대한 핑 과 동일 합니다.

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

이:

  • group두 번째 튜플 부분 ( _._2) ( 그룹 Map 의 그룹 부분)을 기반으로하는 s 요소

  • map첫 번째 튜플 부분 ( _._1) 을 취하여 그룹화 된 항목 ( Map 그룹의 부분 매핑 )

이것은으로 볼 수있는 원 - 패스 버전map.groupBy(_._2).mapValues(_.map(_._1)).


너무 나쁜 그것은 변환되지 않습니다 Map[K, C[V]]Map[V, C[K]].
jwvh

0
  1. 역은이 연산에 대해 역보다 더 나은 이름입니다 ( "수학적 함수의 역"에서와 같이).

  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
    }

일대일 맵인 경우, 간단하게 테스트하고 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.