두 개의 맵을 병합하고 동일한 키의 값을 합하는 가장 좋은 방법은 무엇입니까?
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
}
The Map based implementation of the Monoid trait that merges two maps.
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
}
Now, if you have a list of maps that needs to be merged (in this case, only 2), it can be done like below.
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) ) ) )
I wrote a blog post about this , check it out :
http://www.nimrodstech.com/scala-map-merge/
basically using scalaz semi group you can achieve this pretty easily
would look something like :
import scalaz.Scalaz._
map1 |+| map2
You can also do that with Cats.
import cats.implicits._
val map1 = Map(1 -> 9 , 2 -> 20)
val map2 = Map(1 -> 100, 3 -> 300)
map1 combine map2 // Map(2 -> 20, 1 -> 109, 3 -> 300)
Starting Scala 2.13
, another solution only based on the standard library consists in replacing the groupBy
part of your solution with groupMapReduce
which (as its name suggests) is an equivalent of a groupBy
followed by mapValues
and a reduce step:
// val map1 = Map(1 -> 9, 2 -> 20)
// val map2 = Map(1 -> 100, 3 -> 300)
(map1.toSeq ++ map2).groupMapReduce(_._1)(_._2)(_+_)
// Map[Int,Int] = Map(2 -> 20, 1 -> 109, 3 -> 300)
This:
Concatenates the two maps as a sequence of tuples (
List((1,9), (2,20), (1,100), (3,300))
). For conciseness,map2
is implicitly converted toSeq
to adapt to the type ofmap1.toSeq
- but you could choose to make it explicit by usingmap2.toSeq
,group
s elements based on their first tuple part (group part of groupMapReduce),map
s grouped values to their second tuple part (map part of groupMapReduce),reduce
s mapped values (_+_
) by summing them (reduce part of groupMapReduce).
Andrzej Doyle's answer contains a great explanation of semigroups which allows you to use the |+|
operator to join two maps and sum the values for matching keys.
There are many ways something can be defined to be an instance of a typeclass, and unlike the OP you might not want to sum your keys specifically. Or, you might want to do operate on a union rather than an intersection. Scalaz also adds extra functions to Map
for this purpose:
You can do
import scalaz.Scalaz._
map1 |+| map2 // As per other answers
map1.intersectWith(map2)(_ + _) // Do things other than sum the values
The fastest and simplest way:
val m1 = Map(1 -> 1.0, 3 -> 3.0, 5 -> 5.2)
val m2 = Map(0 -> 10.0, 3 -> 3.0)
val merged = (m2 foldLeft m1) (
(acc, v) => acc + (v._1 -> (v._2 + acc.getOrElse(v._1, 0.0)))
)
By this way, each of element's immediately added to map.
The second ++
way is:
map1 ++ map2.map { case (k,v) => k -> (v + map1.getOrElse(k,0)) }
Unlike the first way, In a second way for each element in a second map a new List will be created and concatenated to the previous map.
The case
expression implicitly creates a new List using unapply
method.
Here's what I ended up using:
(a.toSeq ++ b.toSeq).groupBy(_._1).mapValues(_.map(_._2).sum)
This is what I came up with...
def mergeMap(m1: Map[Char, Int], m2: Map[Char, Int]): Map[Char, Int] = {
var map : Map[Char, Int] = Map[Char, Int]() ++ m1
for(p <- m2) {
map = map + (p._1 -> (p._2 + map.getOrElse(p._1,0)))
}
map
}
I've got a small function to do the job, it's in my small library for some frequently used functionality which isn't in standard lib. It should work for all types of maps, mutable and immutable, not only HashMaps
Here is the usage
scala> import com.daodecode.scalax.collection.extensions._
scala> val merged = Map("1" -> 1, "2" -> 2).mergedWith(Map("1" -> 1, "2" -> 2))(_ + _)
merged: scala.collection.immutable.Map[String,Int] = Map(1 -> 2, 2 -> 4)
https://github.com/jozic/scalax-collection/blob/master/README.md#mergedwith
And here's the body
def mergedWith(another: Map[K, V])(f: (V, V) => V): Repr =
if (another.isEmpty) mapLike.asInstanceOf[Repr]
else {
val mapBuilder = new mutable.MapBuilder[K, V, Repr](mapLike.asInstanceOf[Repr])
another.foreach { case (k, v) =>
mapLike.get(k) match {
case Some(ev) => mapBuilder += k -> f(ev, v)
case _ => mapBuilder += k -> v
}
}
mapBuilder.result()
}
'Programing' 카테고리의 다른 글
'create_date'시간 소인 필드의 유효하지 않은 기본값 (0) | 2020.05.30 |
---|---|
연관 배열에서 키와 그 값을 제거하려면 어떻게해야합니까? (0) | 2020.05.30 |
CSS를 사용하여 사용자 정의 글꼴을 사용하십니까? (0) | 2020.05.30 |
Base64 문자열을 BitMap 이미지로 변환하여 ImageView에 표시하는 방법은 무엇입니까? (0) | 2020.05.30 |
안드로이드에서 타이머를 설정하는 방법 (0) | 2020.05.29 |