val map1 = Map(1 -> 9 , 2 -> 20)
val map2 = Map(1 -> 100, 3 -> 300)
그것들을 병합하고 동일한 키의 값을 합산하고 싶습니다. 결과는 다음과 같습니다.
Map(2->20, 1->109, 3->300)
이제 두 가지 해결책이 있습니다.
val list = map1.toList ++ map2.toList
val merged = list.groupBy ( _._1) .map { case (k,v) => k -> v.map(_._2).sum }
과
val merged = (map1 /: map2) { case (map, (k,v)) =>
map + ( k -> (v + map.getOrElse(k, 0)) )
}
그러나 더 나은 솔루션이 있는지 알고 싶습니다.
답변
Scalaz 는 세미 그룹 ( Semigroup) 이라는 개념을 가지고 있습니다. 세미 그룹 은 여기에서하고 싶은 일을 포착하여 가장 짧거나 가장 깨끗한 솔루션으로 이끌어줍니다.
scala> import scalaz._
import scalaz._
scala> import Scalaz._
import Scalaz._
scala> val map1 = Map(1 -> 9 , 2 -> 20)
map1: scala.collection.immutable.Map[Int,Int] = Map(1 -> 9, 2 -> 20)
scala> val map2 = Map(1 -> 100, 3 -> 300)
map2: scala.collection.immutable.Map[Int,Int] = Map(1 -> 100, 3 -> 300)
scala> map1 |+| map2
res2: scala.collection.immutable.Map[Int,Int] = Map(1 -> 109, 3 -> 300, 2 -> 20)
특히, 이항 연산자 Map[K, V]
는 맵의 키 를 결합하여 V
중복 값 위에 세미 그룹 연산자를 접습니다 . 표준 세미 그룹 Int
은 더하기 연산자 를 사용하므로 각 중복 키에 대한 값의 합계를 얻습니다.
편집 : user482745의 요청에 따라 조금 더 자세히.
수학적으로 세미 그룹 은 값 집합이며 해당 집합에서 두 개의 값을 가져와 해당 집합에서 다른 값을 생성하는 연산자와 함께 사용됩니다. 따라서 추가중인 정수는 세미 그룹입니다. 예를 들어 +
연산자는 두 개의 정수를 결합하여 다른 정수를 만듭니다.
또한 “주어진 키 유형 및 값 유형을 가진 모든 맵”세트에 대해 세미 그룹을 정의 할 수 있습니다. 두 맵을 결합하여 새로운 맵을 생성하는 조작을 수행 할 수있는 한 입력.
두 맵에 모두 키가 없으면 사소한 것입니다. 두 키에 동일한 키가 존재하는 경우 키가 매핑되는 두 값을 결합해야합니다. 흠, 우리는 같은 유형의 두 엔티티를 결합하는 연산자를 설명하지 않았습니까? 이것이 Scalaz에서 semigroup for Map[K, V]
가 존재하는 경우에만 semigroup for 가 존재 하는 이유입니다.-semigroup for V
– V
semigroup은 동일한 키에 할당 된 두 맵의 값을 결합하는 데 사용됩니다.
따라서 Int
여기에 값 유형이 있기 때문에 1
키 의 “충돌” 은 두 개의 매핑 된 값을 정수로 추가하여 해결됩니다 (Int의 세미 그룹 연산자가하는 것과 같이) 100 + 9
. 값이 문자열 인 경우 충돌로 인해 두 매핑 된 값의 문자열 연결이 발생했습니다 (다시 말하면 문자열에 대한 반 그룹 연산자가 수행하기 때문입니다).
(문자열 연결은 교환 법칙이 성립하지 않기 때문에 그리고 흥미롭게도, -,된다 "a" + "b" != "b" + "a"
-. 반군 결과 작업은 그래서 어느 아닌 map1 |+| map2
다른 map2 |+| map1
문자열의 경우가 아니라 지능의 경우.)
답변
내가 아는 가장 짧은 대답은 표준 라이브러리 만 사용한다는 것입니다.
map1 ++ map2.map{ case (k,v) => k -> (v + map1.getOrElse(k,0)) }
답변
빠른 솔루션 :
(map1.keySet ++ map2.keySet).map {i=> (i,map1.getOrElse(i,0) + map2.getOrElse(i,0))}.toMap
답변
자, 스칼라 라이브러리 (적어도 2.10에서)에는 병합 된 함수가 있습니다. 그러나 그것은지도가 아닌 HashMap에만 표시됩니다. 다소 혼란 스럽다. 또한 서명이 번거 롭습니다. 왜 키가 두 번 필요한지, 언제 다른 키와 쌍을 만들어야하는지 상상할 수 없습니다. 그럼에도 불구하고 이전 “기본”솔루션보다 훨씬 깨끗하고 효과적입니다.
val map1 = collection.immutable.HashMap(1 -> 11 , 2 -> 12)
val map2 = collection.immutable.HashMap(1 -> 11 , 2 -> 12)
map1.merged(map2)({ case ((k,v1),(_,v2)) => (k,v1+v2) })
또한 scaladoc에서
이
merged
방법은 순회를 수행하고 새로운 불변 해시 맵을 처음부터 재구성하는 것보다 평균적으로 성능이 뛰어납니다++
.
답변
평범한 스칼라만으로도 Monoid 로 구현할 수 있습니다 . 다음은 샘플 구현입니다. 이 방법을 사용하면 2 개가 아니라 맵 목록을 병합 할 수 있습니다.
// Monoid trait
trait Monoid[M] {
def zero: M
def op(a: M, b: M): M
}
두 개의 맵을 병합하는 Monoid 특성의 맵 기반 구현입니다.
val mapMonoid = new Monoid[Map[Int, Int]] {
override def zero: Map[Int, Int] = Map()
override def op(a: Map[Int, Int], b: Map[Int, Int]): Map[Int, Int] =
(a.keySet ++ b.keySet) map { k =>
(k, a.getOrElse(k, 0) + b.getOrElse(k, 0))
} toMap
}
이제 병합해야하는 맵 목록 (이 경우에는 2 개만)이 있으면 아래와 같이 수행 할 수 있습니다.
val map1 = Map(1 -> 9 , 2 -> 20)
val map2 = Map(1 -> 100, 3 -> 300)
val maps = List(map1, map2) // The list can have more maps.
val merged = maps.foldLeft(mapMonoid.zero)(mapMonoid.op)
답변
map1 ++ ( for ( (k,v) <- map2 ) yield ( k -> ( v + map1.getOrElse(k,0) ) ) )
답변
나는 이것에 관한 블로그 게시물을 썼다.
http://www.nimrodstech.com/scala-map-merge/
기본적으로 scalaz semi 그룹을 사용하면이를 쉽게 달성 할 수 있습니다
다음과 같이 보일 것입니다 :
import scalaz.Scalaz._
map1 |+| map2