İki haritayı birleştirmenin ve aynı anahtarın değerlerini toplamanın en iyi yolu?


179
val map1 = Map(1 -> 9 , 2 -> 20)
val map2 = Map(1 -> 100, 3 -> 300)

Onları birleştirmek ve aynı anahtarların değerlerini toplamak istiyorum. Sonuç şu olacaktır:

Map(2->20, 1->109, 3->300)

Şimdi 2 çözümüm var:

val list = map1.toList ++ map2.toList
val merged = list.groupBy ( _._1) .map { case (k,v) => k -> v.map(_._2).sum }

ve

val merged = (map1 /: map2) { case (map, (k,v)) =>
    map + ( k -> (v + map.getOrElse(k, 0)) )
}

Ama daha iyi çözüm olup olmadığını bilmek istiyorum.


En kolaymap1 ++ map2
Seraf

3
@Seraf Bu, aslında değerleri birleştirmek yerine kopyaları yok sayarak haritaları basitçe "birleştirir".
Zeynep Akkalyoncu Yılmaz

Yılmaz sağ soruyu daha iyi okumalı, utanç içinde bırakır
Seraf

Yanıtlar:


143

Scalaz bir kavramı vardır Yarıgrup tartışmasız en kısa / en temiz çözüm burada ne yapmak istediğini yakalar, ve başları:

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)

Özellikle, ikili operatör Map[K, V]haritaların tuşlarını birleştirir, Vyarıgrup operatörünü yinelenen değerler üzerine katlar. İçin standart yarıgrup Inttoplama operatörünü kullanır, böylece her yinelenen anahtar için değerlerin toplamını alırsınız.

Düzenle : user482745'nin isteğine göre biraz daha fazla ayrıntı.

Matematiksel olarak bir yarıgrup , sadece bir değer kümesidir, bu kümeden iki değer alan ve bu kümeden başka bir değer üreten bir işleç ile birlikte. Eklenen tamsayılar bir yarıgruptur, örneğin - +operatör başka bir int yapmak için iki int birleştirir.

"Belirli bir anahtar türüne ve değer türüne sahip tüm haritalar" kümesi üzerinde bir yarı grup tanımlayabilirsiniz, ancak iki haritayı bir şekilde birleştirerek, iki haritayı bir şekilde birleştirerek, iki haritayı bir araya getirebilirsiniz. girişleri.

Her iki haritada da görünen anahtar yoksa, bu önemsizdir. Her iki haritada da aynı anahtar varsa, anahtarın eşlendiği iki değeri birleştirmemiz gerekir. Hmm, aynı türden iki varlığı birleştiren bir operatör tanımladık mı? Scalaz içinde bir yarıgrubudur nedeni budur Map[K, V]ancak ve ancak bir Yarıgrup için eğer varsa Vvar - V'ın yarıgrubudur aynı anahtar atanır iki harita değerleri birleştirmek için kullanılır.

Bu nedenle Intburada değer türü olduğundan , 1anahtardaki "çarpışma" iki eşlenen değerin tam olarak eklenmesi ile çözülür (Int'nin yarıgrup operatörünün yaptığı gibi) 100 + 9. Değerler Dizeler olsaydı, bir çarpışma iki eşlenen değerin dize birleştirmesine neden olurdu (yine Dize için yarıgrup operatörünün yaptığı şey budur).

(Ve ilginç bir şekilde, dize birleştirmesi değişmeli olmadığından - yani, "a" + "b" != "b" + "a"sonuçta ortaya çıkan semigroup işlemi de değil. Dize durumundakinden map1 |+| map2farklı map2 |+| map1, ancak Int durumunda değil.)


37
Parlak! Anlamlı olduğu ilk uygulama örneği scalaz.
soc

5
Şaka yapmıyorum! Eğer aramaya başlarsan ... her yerde. Erric torrebone specs ve specs2 alıntılamak için: "Önce Seçeneği öğrenmek ve her yerde görmeye başlar. Sonra Uygulamalı öğrenir ve aynı şey. Sonraki?" Sırada daha da işlevsel kavramlar var. Ve bunlar kodunuzu yapılandırmanıza ve sorunları güzelce çözmenize yardımcı olur.
AndreasScheinert

4
Aslında, sonunda Scala'yı bulduğumda beş yıldır Option'ı arıyordum. Boş olabilecek bir Java nesnesi başvurusu ile (yani Ave arasında Option[A]) olamayacak kadar büyük bir fark, gerçekten aynı tür olduklarına inanamadım. Ben sadece Scalaz bakmaya başladı. Yeterince zeki olduğumdan emin değilim ...
Malvolio

1
Java için Seçenek de vardır, bkz. İşlevsel Java. Hiç korkunuz yok, öğrenmek eğlencelidir. Ve fonksiyonel programlama size sadece yeni şeyler öğretmez, bunun yerine programcıya problemlerle başa çıkmak için terimler, kelime bilgisi sağlama konusunda yardım sunar. OP sorusu mükemmel bir örnek. Bir Semigroup kavramı o kadar basit ki her gün örneğin Dizeler için kullanıyorsunuz. Gerçek güç, bu soyutlamayı belirlerseniz, adlandırır ve son olarak sadece String olmak üzere diğer türlere uygularsanız görünür.
AndreasScheinert

1
1 -> (100 + 9) ile sonuçlanması nasıl mümkün olur? Bana "yığın izini" gösterebilir misin? Teşekkürler. Not: Burada cevabı daha açık hale getirmesini istiyorum.
user482745

152

Sadece standart kütüphaneyi kullandığını bildiğim en kısa cevap

map1 ++ map2.map{ case (k,v) => k -> (v + map1.getOrElse(k,0)) }

34
Güzel çözüm. ++Solda ++(k, _) varsa, (k, _) 'nin sol tarafındaki haritadan (burada harita1) (k, v) ile yer değiştiren ipucunu eklemeyi severim. yan harita (burada map1), ör.Map(1->1) ++ Map(1->2) results in Map(1->2)
Lutz

Bir çeşit daha temiz versiyon: ((k, v) <- (aa ++ bb)) verim k -> (eğer ((aa k içerir) && (bb k içerir)) aa (k) + v başka v)
dividebyzero

Daha önce bir şey farklı yaptım, ama işte yaptığınız şeyin bir versiyonu, haritayı değiştirerek formap1 ++ (((k, v) <- map2) verimi k -> (v + map1.getOrElse (k, 0) )))
dividebyzero

1
Jus12 - No. .daha yüksek önceliğe sahiptir ++; map1 ++ map2.map{...}olarak okudunuz map1 ++ (map2 map {...}). Öyleyse bir şekilde map1unsurları haritalandırıyorsunuz , diğer şekilde yapmıyorsunuz.
Rex Kerr

1
@matt - Scalaz zaten yapacak, bu yüzden "varolan bir kütüphane zaten yapıyor" derdim.
Rex Kerr

48

Hızlı çözüm:

(map1.keySet ++ map2.keySet).map {i=> (i,map1.getOrElse(i,0) + map2.getOrElse(i,0))}.toMap

41

Şimdi, scala kütüphanesinde (en azından 2.10'da) istediğiniz bir şey var - birleştirilmiş işlev. AMA Harita'da değil sadece HashMap'te sunulur. Biraz kafa karıştırıcı. Ayrıca imza hantal - neden iki kez bir anahtara ihtiyacım olduğunu ve başka bir anahtarla bir çift üretmem gerektiğini düşünemiyorum. Ancak yine de, önceki "doğal" çözümlerden çok daha temiz ve çalışır.

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) })

Ayrıca scaladoc da

mergedYöntem, bir geçişi yapıyor ve sıfırdan yeni değişmez karma harita yeniden veya daha ortalama daha fazla ölçülebilir üzerindedir ++.


1
Şu an itibariyle, değişmez Hashmap'te değil, sadece değişmez Hashmap'te.
Kevin Wheeler

2
Bu sadece HashMaps dürüst olması için çok can sıkıcı bir durum.
Johan S

Bunu derlemek için alamıyorum, kabul ettiği tür özel gibi görünüyor, bu yüzden eşleşen bir yazılı işlevi geçemiyorum.
Ryan The Leach

2
2.11 sürümünde bir şey değişmiş gibi görünüyor. 2.10 scaladoc'a göz atın - scala-lang.org/api/2.10.1/… Normal bir işlev vardır. Ama 2.11'de MergeFunction.
Mikhail Golubtsov

private type MergeFunction[A1, B1] = ((A1, B1), (A1, B1)) => (A1, B1)
2.11'de

14

Bu sadece sade Scala ile Monoid olarak uygulanabilir . İşte örnek bir uygulama. Bu yaklaşımla, sadece 2 değil, bir harita listesini birleştirebiliriz.

// Monoid trait

trait Monoid[M] {
  def zero: M
  def op(a: M, b: M): M
}

İki haritayı birleştiren Monoid özelliğinin Harita tabanlı uygulaması.

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
}

Şimdi, birleştirilmesi gereken haritaların bir listesi varsa (bu durumda, sadece 2), aşağıdaki gibi yapılabilir.

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)

5
map1 ++ ( for ( (k,v) <- map2 ) yield ( k -> ( v + map1.getOrElse(k,0) ) ) )

5

Bununla ilgili bir blog yazısı yazdım, bir göz atın:

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

temelde scalaz yarı grubu kullanarak bunu kolayca elde edebilirsiniz

şöyle görünecektir:

  import scalaz.Scalaz._
  map1 |+| map2

11
Cevabınıza biraz daha ayrıntı vermeniz gerekiyor, tercihen bazı uygulama kodları. Bunu, gönderdiğiniz diğer benzer cevaplar için de yapın ve her bir yanıtı, sorulan soruya uyarlayın. Temel Kural: Asker blog linkine tıklamadan cevabınızdan yararlanabilmelidir.
Robert Harvey

5

Ayrıca ile yapabilirsiniz Kediler .

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)

Eek import cats.implicits._,. İthalat import cats.instances.map._ import cats.instances.int._ import cats.syntax.semigroup._daha ayrıntılı değil ...
St.Antario

@ St.Antario aslında sadece sahip olmak için önerilen yolimport cats.implicits._
Artsiom Miklushou

Kim tarafından önerilen? Tümü (çoğu kullanılmayan) örtülü örnekleri kapsam içine almak derleyicinin hayatını zorlaştırır. Ve eğer biri gerekli değilse, örneğin, Uygulamalı örnek, neden oraya getirirler?
St.Antario

4

Başlangıçta Scala 2.13, sadece standart kütüphaneye dayanan başka bir çözüm groupBy, çözümünüzün groupMapReduce(adından da anlaşılacağı gibi) groupByardından gelen mapValuesve azaltma adımına eşdeğer olan kısmını değiştirmekten oluşur :

// 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)

Bu:

  • İki haritayı bir grup tuple ( List((1,9), (2,20), (1,100), (3,300))) olarak birleştirir. Özlü, map2bir örtülü dönüştürülür Seqtipine adapte map1.toSeq- ancak kullanarak bunu açıkça yapmak için tercih edebilirsiniz map2.toSeq,

  • groupilk tuple parçasının (grup parçası göre elemanları s grup MapReduce),

  • maps değerlerini ikinci grup parçalarına gruplandırır ( Harita Küçült grubunun harita kısmı ),

  • reduces eşlenen değerleri ( _+_) toplayarak (groupMap Reduce öğesinin bir kısmını azalt ).


3

İşte ben kullanarak sona erdi:

(a.toSeq ++ b.toSeq).groupBy(_._1).mapValues(_.map(_._2).sum)

1
Bu gerçekten OP tarafından önerilen ilk çözümden önemli ölçüde farklı değil.
jwvh

2

Andrzej Doyle'un cevabı, |+|iki haritayı birleştirmek ve eşleşen anahtarların değerlerini toplamak için operatörü kullanmanıza izin veren yarı grupların harika bir açıklamasını içerir .

Bir şeyin bir sınıf sınıfının bir örneği olarak tanımlanmasının birçok yolu vardır ve OP'nin aksine, anahtarlarınızı özel olarak toplamak istemeyebilirsiniz. Veya bir kavşak yerine sendika üzerinde işlem yapmak isteyebilirsiniz. Scalaz ayrıcaMap bu amaç için :

https://oss.sonatype.org/service/local/repositories/snapshots/archive/org/scalaz/scalaz_2.11/7.3.0-SNAPSHOT/scalaz_2.11-7.3.0-SNAPSHOT-javadoc.jar/!/ index.html # scalaz.std.MapFunctions

Yapabilirsin

import scalaz.Scalaz._

map1 |+| map2 // As per other answers
map1.intersectWith(map2)(_ + _) // Do things other than sum the values

2

En hızlı ve en basit yol:

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)))
)

Bu şekilde, elemanların her biri hemen haritaya eklenir.

İkinci ++yol:

map1 ++ map2.map { case (k,v) => k -> (v + map1.getOrElse(k,0)) }

İlk yoldan farklı olarak, ikinci bir haritadaki her öğe için ikinci bir şekilde yeni bir Liste oluşturulacak ve önceki haritaya birleştirilecektir.

caseİfadesi örtük kullanarak yeni Listesini oluşturur unapplyyöntemi.


1

Ben de bunu buldum ...

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
}

1

Typeclass desenini kullanarak, herhangi bir Sayısal türü birleştirebiliriz:

object MapSyntax {
  implicit class MapOps[A, B](a: Map[A, B]) {
    def plus(b: Map[A, B])(implicit num: Numeric[B]): Map[A, B] = {
      b ++ a.map { case (key, value) => key -> num.plus(value, b.getOrElse(key, num.zero)) }
    }
  }
}

Kullanımı:

import MapSyntax.MapOps

map1 plus map2

Bir dizi haritayı birleştirme:

maps.reduce(_ plus _)

0

İşi yapmak için küçük bir fonksiyonum var, standart kütüphanede olmayan bazı sık kullanılan işlevler için küçük kütüphanemde. Yalnızca HashMaps için değil, değiştirilebilir ve değiştirilemez tüm harita türleri için çalışmalıdır

İşte kullanımı

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

Ve işte vücut

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()
  }

https://github.com/jozic/scalax-collection/blob/master/src%2Fmain%2Fscala%2Fcom%2Fdaodecode%2Fscalax%2Fcollection%2Fextensions%2Fpackage.scala#L190

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.