Scala :지도 모음을 병합하는 방법
-
13-09-2019 - |
문제
지도 [문자열, 이중] 목록이 있으며 내용을 단일 맵 [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
필요한 것을 정확히 수행합니다.
- 목록을 단일 맵으로 병합합니다
- 중복 키를 제거합니다
예시:
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
, 또 다른 솔루션 중복 키를 처리합니다 그리고 전용입니다 표준 라이브러리를 기반으로합니다 합병으로 구성됩니다 Map
S 시퀀스로서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)
이것:
flatten
s (컨텍스트) 튜플 시퀀스로서의 맵 (List(("hello", 1.1), ("world", 2.2), ("goodbye", 3.3), ("hello", 4.4))
), 모든 키/값을 유지하는 (중복 키조차도)group
첫 번째 튜플 부분을 기반으로 한 S 요소 (_._1
) (그룹 부분 그룹Mapreduce)map
S 그룹화 된 값은 두 번째 튜플 부분 (_._2
) (그룹의지도 부분지도줄이다)reduce
s 매핑 그룹화 된 값 (_+_
) 합계를 취함으로써 (그러나 그것은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(_)(_ + _) }