문제

지도 [문자열, 이중] 목록이 있으며 내용을 단일 맵 [String, Double]으로 병합하고 싶습니다. 관용적 인 방식으로 어떻게해야합니까? 나는 이것을 접을 수 있어야한다고 생각합니다. 같은 것 :

val newMap = Map[String, Double]() /: listOfMaps { (accumulator, m) => ... }

또한 주요 충돌을 일반적인 방식으로 처리하고 싶습니다. 즉, 이미 존재하는 맵에 키를 추가하면 더블 (이 경우)을 반환하는 함수를 지정하고 해당 키의 기존 값을 가져 와서 추가하려는 값을 지정할 수 있어야합니다. . 키가 아직지도에 존재하지 않으면 맵과 그 값을 변경하지 않으면 추가하십시오.

내 특정 경우에 맵에 이미 키가 포함 된 경우 더블이 기존 맵 값에 추가되도록 단일 맵 [String, Double]을 작성하려고합니다.

특정 코드에서 Mutable Maps로 작업하고 있지만 가능한 경우보다 일반적인 솔루션에 관심이 있습니다.

도움이 되었습니까?

해결책

이건 어때:

def mergeMap[A, B](ms: List[Map[A, B]])(f: (B, B) => B): Map[A, B] =
  (Map[A, B]() /: (for (m <- ms; kv <- m) yield kv)) { (a, kv) =>
    a + (if (a.contains(kv._1)) kv._1 -> f(a(kv._1), kv._2) else kv)
  }

val ms = List(Map("hello" -> 1.1, "world" -> 2.2), Map("goodbye" -> 3.3, "hello" -> 4.4))
val mm = mergeMap(ms)((v1, v2) => v1 + v2)

println(mm) // prints Map(hello -> 5.5, world -> 2.2, goodbye -> 3.3)

그리고 그것은 2.7.5와 2.8.0에서 작동합니다.

다른 팁

글쎄, 당신은 할 수 있습니다 :

mapList reduce (_ ++ _)

충돌에 대한 특별 요구 사항을 제외하고.

특별한 요구 사항이 있으므로 아마도 최선은 다음과 같은 일을 할 것입니다 (2.8).

def combine(m1: Map, m2: Map): Map = {
  val k1 = Set(m1.keysIterator.toList: _*)
  val k2 = Set(m2.keysIterator.toList: _*)
  val intersection = k1 & k2

  val r1 = for(key <- intersection) yield (key -> (m1(key) + m2(key)))
  val r2 = m1.filterKeys(!intersection.contains(_)) ++ m2.filterKeys(!intersection.contains(_)) 
  r2 ++ r1
}

그런 다음 내 라이브러리 패턴을 통해이 메소드를지도 클래스에 추가하고 원래 예제에서 사용할 수 있습니다. "++":

class CombiningMap(m1: Map[Symbol, Double]) {
  def combine(m2: Map[Symbol, Double]) = {
    val k1 = Set(m1.keysIterator.toList: _*)
    val k2 = Set(m2.keysIterator.toList: _*)
    val intersection = k1 & k2
    val r1 = for(key <- intersection) yield (key -> (m1(key) + m2(key)))
    val r2 = m1.filterKeys(!intersection.contains(_)) ++ m2.filterKeys(!intersection.contains(_))
    r2 ++ r1
  }
}

// Then use this:
implicit def toCombining(m: Map[Symbol, Double]) = new CombiningMap(m)

// And finish with:
mapList reduce (_ combine _)

이것은 2.8로 작성된 동안 keysIterator becomes keys 2.7, filterKeys 측면에서 작성해야 할 수도 있습니다 filter 그리고 map, & becomes **, 그리고 너무 다르지 않아야합니다.

아직 아무도이 해결책을 제시하지 않았다는 것에 놀랐습니다.

myListOfMaps.flatten.toMap

필요한 것을 정확히 수행합니다.

  1. 목록을 단일 맵으로 병합합니다
  2. 중복 키를 제거합니다

예시:

scala> List(Map('a -> 1), Map('b -> 2), Map('c -> 3), Map('a -> 4, 'b -> 5)).flatten.toMap
res7: scala.collection.immutable.Map[Symbol,Int] = Map('a -> 4, 'b -> 5, 'c -> 3)

flatten 지도 목록을 튜플의 평평한 목록으로 바꾸고 toMap 튜플 목록을 모든 중복 키가 제거 된 상태에서 맵으로 바꿉니다.

이 질문을 빨리 읽으므로 무언가를 놓친 것인지 확실하지 않습니다 (2.7.x에서 작동 해야하는 것처럼 또는 Scalaz가 없음) :

import scalaz._
import Scalaz._
val ms = List(Map("hello" -> 1.1, "world" -> 2.2), Map("goodbye" -> 3.3, "hello" -> 4.4))
ms.reduceLeft(_ |+| _)
// returns Map(goodbye -> 3.3, hello -> 5.5, world -> 2.2)

이중에 대한 모노이드 정의를 변경하고 값을 축적하는 다른 방법을 얻을 수 있습니다. 여기서 최대를 얻습니다.

implicit val dbsg: Semigroup[Double] = semigroup((a,b) => math.max(a,b))
ms.reduceLeft(_ |+| _)
// returns Map(goodbye -> 3.3, hello -> 4.4, world -> 2.2)

흥미롭고, 이것을 조금이면서, 나는 다음을 얻었습니다 (2.7.5).

일반지도 :

   def mergeMaps[A,B](collisionFunc: (B,B) => B)(listOfMaps: Seq[scala.collection.Map[A,B]]): Map[A, B] = {
    listOfMaps.foldLeft(Map[A, B]()) { (m, s) =>
      Map(
        s.projection.map { pair =>
        if (m contains pair._1)
          (pair._1, collisionFunc(m(pair._1), pair._2))
        else
          pair
      }.force.toList:_*)
    }
  }

그러나 인간, 그것은 투영과 강제력과 톨리스트와 무엇이 끔찍한 지에 끔찍합니다. 별도의 질문 : 접힘 내에서이를 다루는 더 좋은 방법은 무엇입니까?

내 코드에서 다루고 있던 Mutable Maps의 경우, 덜 일반적인 솔루션으로 다음을 얻었습니다.

def mergeMaps[A,B](collisionFunc: (B,B) => B)(listOfMaps: List[mutable.Map[A,B]]): mutable.Map[A, B] = {
    listOfMaps.foldLeft(mutable.Map[A,B]()) {
      (m, s) =>
      for (k <- s.keys) {
        if (m contains k)
          m(k) = collisionFunc(m(k), s(k))
        else
          m(k) = s(k)
      }
      m
    }
  }

그것은 약간 깨끗해 보이지만, 쓰여진 것처럼 변한 맵에서만 작동합니다. 흥미롭게도, 나는 처음에 (질문을하기 전에) 폴드 프트 대신 /: 유형 오류를 받고있었습니다. 나는 /: 그리고 foldleft는 기본적으로 동일했지만 컴파일러는 (m, s)에 대한 명시 적 유형이 필요하다고 불평했다. 그게 무슨 일이야?

나는 이것에 대한 블로그 게시물을 썼다.

http://www.nimrodstech.com/scala-map-merge/

기본적으로 Scalaz Semi Group을 사용하여 쉽게 달성 할 수 있습니다.

다음과 같은 것 같습니다.

  import scalaz.Scalaz._
  listOfMaps reduce(_ |+| _)

시작 Scala 2.13, 또 다른 솔루션 중복 키를 처리합니다 그리고 전용입니다 표준 라이브러리를 기반으로합니다 합병으로 구성됩니다 MapS 시퀀스로서flatten) 새를 적용하기 전에 GroupMapReduce (이름에서 알 수 있듯이)는 groupBy 그 다음에 맵핑과 그룹화 된 값의 감소 단계가 이어집니다.

List(Map("hello" -> 1.1, "world" -> 2.2), Map("goodbye" -> 3.3, "hello" -> 4.4))
  .flatten
  .groupMapReduce(_._1)(_._2)(_ + _)
// Map("world" -> 2.2, "goodbye" -> 3.3, "hello" -> 5.5)

이것:

  • flattens (컨텍스트) 튜플 시퀀스로서의 맵 (List(("hello", 1.1), ("world", 2.2), ("goodbye", 3.3), ("hello", 4.4))), 모든 키/값을 유지하는 (중복 키조차도)

  • group첫 번째 튜플 부분을 기반으로 한 S 요소 (_._1) (그룹 부분 그룹Mapreduce)

  • mapS 그룹화 된 값은 두 번째 튜플 부분 (_._2) (그룹의지도 부분지도줄이다)

  • reduces 매핑 그룹화 된 값 (_+_) 합계를 취함으로써 (그러나 그것은 reduce: (T, T) => T 함수) (GroupMap의 일부를 줄입니다줄이다)


그만큼 groupMapReduce 단계는 a로 볼 수 있습니다 1 패스 버전 동등한 :

list.groupBy(_._1).mapValues(_.map(_._2).reduce(_ + _))

Scalaz를 사용하는 것만 큼 깨끗하게 사용되는 OneLiner 헬퍼 핵 :

def mergeMaps[K,V](m1: Map[K,V], m2: Map[K,V])(f: (V,V) => V): Map[K,V] =
    (m1 -- m2.keySet) ++ (m2 -- m1.keySet) ++ (for (k <- m1.keySet & m2.keySet) yield { k -> f(m1(k), m2(k)) })

val ms = List(Map("hello" -> 1.1, "world" -> 2.2), Map("goodbye" -> 3.3, "hello" -> 4.4))
ms.reduceLeft(mergeMaps(_,_)(_ + _))
// returns Map(goodbye -> 3.3, hello -> 5.5, world -> 2.2)

궁극적 인 가독성을 위해 암시 적 사용자 지정 유형으로 마무리합니다.

class MyMap[K,V](m1: Map[K,V]) {
    def merge(m2: Map[K,V])(f: (V,V) => V) =
    (m1 -- m2.keySet) ++ (m2 -- m1.keySet) ++ (for (k <- m1.keySet & m2.keySet) yield { k -> f(m1(k), m2(k)) })
}
implicit def toMyMap[K,V](m: Map[K,V]) = new MyMap(m)

val ms = List(Map("hello" -> 1.1, "world" -> 2.2), Map("goodbye" -> 3.3, "hello" -> 4.4))
ms reduceLeft { _.merge(_)(_ + _) } 
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top