दो मानचित्रों को मिलाने और एक ही कुंजी के मूल्यों को योग करने का सबसे अच्छा तरीका है?


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

मैं उन्हें मर्ज करना चाहता हूं, और एक ही कुंजी के मूल्यों को जोड़ सकता हूं। तो परिणाम होगा:

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

अब मेरे पास 2 समाधान हैं:

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

लेकिन मैं जानना चाहता हूं कि क्या कोई बेहतर उपाय है।


सबसे सरल हैmap1 ++ map2
सेराफ

3
@ शेरा वास्तव में नक्शे को केवल "विलय" करता है, अपने मूल्यों को समेटने के बजाय डुप्लिकेट को अनदेखा करता है।
Zeynep Akkalyoncu Yilmaz

@ZeynepAkkalyoncuYilmaz अधिकार को प्रश्न को बेहतर ढंग से पढ़ना चाहिए, शर्म छोड़ देता है
सेराफ सिप

जवाबों:


143

स्कलाज़ में एक सेमीग्रुप की अवधारणा है, जो आप यहाँ क्या करना चाहते हैं, कैप्चर करता है, और यकीनन सबसे छोटा / स्वच्छ समाधान निकलता है:

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 के अनुरोध के अनुसार थोड़ा और विस्तार।

गणितीय रूप से एक सेमीग्रुप केवल मानों का एक समूह है, साथ में एक ऑपरेटर जो उस सेट से दो मान लेता है, और उस सेट के लिए एक और मान पैदा करता है। तो इसके अलावा पूर्णांक एक अर्धवृत्त है, उदाहरण के लिए - +ऑपरेटर एक और इंट बनाने के लिए दो इनट्स को जोड़ता है।

आप "किसी दिए गए कुंजी प्रकार और मूल्य प्रकार के साथ सभी मानचित्र" के सेट पर एक उपसमूह को भी परिभाषित कर सकते हैं, इसलिए जब तक आप कुछ ऑपरेशन के साथ आ सकते हैं जो दो नक्शों को मिलाकर एक नया निर्माण करता है जो किसी भी तरह दो का संयोजन है आदानों।

यदि कोई कुंजी नहीं है जो दोनों मानचित्रों में दिखाई देती है, तो यह तुच्छ है। यदि एक ही कुंजी दोनों मानचित्रों में मौजूद है, तो हमें उन दो मूल्यों को संयोजित करने की आवश्यकता है, जिनके लिए मुख्य मानचित्र हैं। हम्म, क्या हमने सिर्फ एक ऑपरेटर का वर्णन नहीं किया है जो एक ही प्रकार की दो संस्थाओं को जोड़ती है? यही कारण है कि स्केलाज़ में एक सेमीग्रुप Map[K, V]मौजूद है अगर और केवल एक सेमीग्रुप Vमौजूद है -V 'एस का उपयोग दो मानचित्रों से मानों को संयोजित करने के लिए किया जाता है जो एक ही कुंजी को सौंपे जाते हैं।

इसलिए क्योंकि Intयहाँ मूल्य प्रकार है, 1कुंजी पर "टकराव" दो मैप किए गए मानों के पूर्णांक जोड़ द्वारा हल किया जाता है (जैसा कि इंट का सेमीग्रुप ऑपरेटर करता है), इसलिए100 + 9 । यदि मान स्ट्रिंग्स थे, तो एक टकराव के परिणामस्वरूप दो मैप किए गए मानों को फिर से जोड़ा जा सकता था (क्योंकि, स्ट्रिंग के लिए अर्धचालक ऑपरेटर यही करता है)।

(और दिलचस्प बात यह है कि, क्योंकि स्ट्रिंग कॉन्फैटिनेशन कम्यूटेटिव नहीं है - अर्थात, "a" + "b" != "b" + "a"परिणामी सेमीग्राफ ऑपरेशन या तो नहीं है। इसलिए स्ट्रिंग मामले में map1 |+| map2अलग है map2 |+| map1, लेकिन इंट केस में नहीं।)


37
प्रतिभाशाली! पहला व्यावहारिक उदाहरण जहां scalazसमझ में आया।
सामाजिक

5
मजाक नहीं! यदि आप इसे ढूंढना शुरू करते हैं ... यह सभी जगह है। स्पेक्स और स्पेक्स 2 के एररिक टॉरबोन लेखक को उद्धृत करने के लिए: "सबसे पहले आप विकल्प सीखते हैं और आप इसे हर जगह देखना शुरू करते हैं। फिर आप एपेरेटिव सीखते हैं और यह एक ही बात है। अगला?" अगला और भी अधिक कार्यात्मक अवधारणाएं हैं। और वे बहुत मदद से आप अपने कोड को संरचना और समस्याओं को अच्छी तरह से हल करते हैं।
AndreasScheinert

4
वास्तव में, मैं पांच साल के लिए विकल्प की तलाश कर रहा था जब मुझे अंत में स्काला मिला। एक जावा ऑब्जेक्ट संदर्भ के बीच का अंतर जो अशक्त हो सकता है और एक जो (यानी के बीच Aऔर Option[A]) इतना विशाल नहीं हो सकता है, मुझे विश्वास नहीं हो रहा था कि वे वास्तव में एक ही प्रकार के थे। मैंने अभी स्कलाज़ देखना शुरू किया। मुझे यकीन नहीं है कि मैं काफी स्मार्ट हूं ...
माल्वोलियो

1
जावा के लिए विकल्प भी है, कार्यात्मक जावा देखें। कोई डर नहीं है, सीखने में मज़ा है। और कार्यात्मक प्रोग्रामिंग आपको नई चीजें (केवल) नहीं सिखाता है, बल्कि आपको समस्याओं से निपटने के लिए प्रोग्रामर, शब्दावली प्रदान करने में मदद करता है। ओपी प्रश्न एक आदर्श उदाहरण है। एक सेमिग्रुप की अवधारणा इतनी सरल है, आप इसे हर दिन उपयोग करते हैं जैसे कि स्ट्रिंग्स। वास्तविक शक्ति प्रकट होती है यदि आप इस अमूर्त की पहचान करते हैं, तो इसे नाम दें और अंत में इसे अन्य प्रकारों पर लागू करें, फिर स्ट्रिंग।
एंड्रियाशेचेर्ट 16

1
यह कैसे संभव है कि इसका परिणाम 1 -> (100 + 9) होगा? क्या आप मुझे "स्टैक ट्रेस" दिखा सकते हैं? धन्यवाद। पुनश्च: मैं यहाँ उत्तर को और अधिक स्पष्ट करने के लिए कह रहा हूँ।
user482745

152

मुझे पता है कि सबसे छोटा जवाब केवल मानक पुस्तकालय का उपयोग करता है

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

34
अच्छा समाधान है। मैं संकेत जोड़ना पसंद करता हूं, जो ++किसी भी (k, v) को मैप के बाईं ओर ++(यहां map1) बाय (k, v) राइट साइड मैप से, यदि (k, _) पहले से बाईं ओर मौजूद है पक्ष नक्शा (यहाँ map1), जैसेMap(1->1) ++ Map(1->2) results in Map(1->2)
लुट्ज़

एक प्रकार का नटवर संस्करण: के लिए ((के, वी) <- (आ ++ बी बी)) उपज के -> (((यदि आ में के) और& (बी बी शामिल हैं) आ (के) + वी और v)
dividebyzero

मैंने पहले कुछ अलग किया था, लेकिन यहां एक ऐसा संस्करण है जो आपने किया, मैप for1 ++ ((k, v) <- map2) के लिए मानचित्र को प्रतिस्थापित करने के बजाय k>> (v + map1.getOrElse (k, 0) )))
dividebyzero

1
@ Jus12 - नहीं .की तुलना में उच्च पूर्वता है ++; आप पढ़ सकते हैं map1 ++ map2.map{...}के रूप में map1 ++ (map2 map {...})। तो एक तरह से आप map1तत्वों को मैप करते हैं, और दूसरा तरीका जो आप नहीं करते हैं।
रेक्स केर

1
@ मट्ट - स्कलाज़ पहले से ही ऐसा करेगा, इसलिए मैं कहूंगा "एक मौजूदा पुस्तकालय पहले से ही ऐसा करता है"।
रेक्स केर


41

ठीक है, अब स्केला लाइब्रेरी में (कम से कम 2.10 में) कुछ ऐसा है जो आप चाहते थे - मर्ज किए गए फ़ंक्शन। लेकिन यह केवल हाशप में प्रस्तुत किया गया है न कि मानचित्र में। यह कुछ गड़बड़ है। इसके अलावा हस्ताक्षर बोझिल है - मैं कल्पना नहीं कर सकता कि मुझे दो बार चाबी की आवश्यकता क्यों होगी और जब मुझे किसी अन्य कुंजी के साथ एक जोड़ी बनाने की आवश्यकता होगी। लेकिन फिर भी, यह पिछले "देशी" समाधानों की तुलना में बहुत अधिक काम करता है।

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

इसके अलावा स्केलडॉक में उल्लेख किया गया है कि

mergedविधि पर एक ट्रेवर्सल कर रहे हैं और शुरू से एक नई अपरिवर्तनीय हैश नक्शे के पुनर्निर्माण, या औसत से अधिक performant है ++


1
फिलहाल, यह केवल अपरिवर्तनीय हैशमैप में है, न कि परिवर्तनशील हैशमैप में।
केविन व्हीलर

2
यह बहुत कष्टप्रद है कि उनके पास केवल इतना है कि HashMaps के लिए ईमानदार होना चाहिए।
जोहान एस

मैं इसे संकलित करने के लिए नहीं मिल सकता, ऐसा लगता है कि यह जिस प्रकार से स्वीकार करता है वह निजी है, इसलिए मैं मेल खाने वाले टाइप फ़ंक्शन में पास नहीं हो सकता।
रयान द लीच

2
2.11 संस्करण में कुछ बदला हुआ लगता है। 2.10 स्केलडॉक देखें - scala-lang.org/api/2.10.1/… एक सामान्य कार्य है। लेकिन 2.11 में MergeFunction
मिखाइल गोलूबत्सोव

2.11 में जो कुछ भी बदल गया है, वह इस विशेष प्रकार के प्रकार के लिए एक अन्य उपनाम का परिचय हैprivate type MergeFunction[A1, B1] = ((A1, B1), (A1, B1)) => (A1, B1)
EthanP

14

यह सिर्फ सादे स्काला के साथ एक मोनॉयड के रूप में लागू किया जा सकता है । यहाँ एक नमूना कार्यान्वयन है। इस दृष्टिकोण के साथ, हम न केवल 2, बल्कि मानचित्रों की एक सूची को मर्ज कर सकते हैं।

// Monoid trait

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

दो मानचित्रों को मिलाने वाले मोनॉयड लक्षण का मानचित्र आधारित कार्यान्वयन।

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)


5

मैंने इस बारे में एक ब्लॉग पोस्ट लिखा है, इसे देखें:

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

मूल रूप से स्केलाज़ अर्ध समूह का उपयोग करके आप इसे बहुत आसानी से प्राप्त कर सकते हैं

कुछ इस तरह दिखेगा:

  import scalaz.Scalaz._
  map1 |+| map2

11
आपको अपने उत्तर में थोड़ा और विस्तार करने की आवश्यकता है, अधिमानतः कुछ कार्यान्वयन कोड। अपने द्वारा पोस्ट किए गए अन्य समान उत्तरों के लिए भी ऐसा करें, और पूछे गए विशिष्ट प्रश्न के प्रत्येक उत्तर को दर्जी करें। अंगूठे का नियम: पूछने वाले को ब्लॉग लिंक पर क्लिक किए बिना आपके उत्तर से लाभ उठाने में सक्षम होना चाहिए।
रॉबर्ट हार्वे

5

आप बिल्लियों के साथ भी ऐसा कर सकते हैं ।

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._। आयात import cats.instances.map._ import cats.instances.int._ import cats.syntax.semigroup._नहीं बहुत अधिक
क्रिया

@ सेंटऑनारियो यह वास्तव में केवल import cats.implicits._
कला के

किसके द्वारा पुनर्प्रकाशित? सभी (जिनमें से अधिकांश का उपयोग नहीं किया गया है) को लाने के दायरे में निहित उदाहरण संकलक के जीवन को जटिल बनाते हैं। और इसके अलावा अगर किसी को ज़रूरत नहीं है, तो कहिए, उदाहरण के लिए वे इसे वहाँ क्यों लाएँगे?
सेंट एँटारियो

4

प्रारंभ करना Scala 2.13, केवल मानक पुस्तकालय पर आधारित एक और समाधान groupByमें आपके समाधान के उस भाग को प्रतिस्थापित करना शामिल है groupMapReduceजिसके साथ (जैसा कि इसके नाम से पता चलता है) एक अनुवर्ती कदम के बराबर groupByहै mapValuesऔर एक कम कदम है:

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

यह:

  • ट्यूपल्स के अनुक्रम के रूप में दो मानचित्रों को समेटता है ( List((1,9), (2,20), (1,100), (3,300)))। संक्षिप्तता के लिए, map2है परोक्ष में बदला Seqके प्रकार के लिए अनुकूल करने के लिए map1.toSeq- लेकिन आप इसे का उपयोग करके स्पष्ट बनाने के लिए चुन सकते हैं map2.toSeq,

  • groupउनके पहले टपल भाग ( समूह MapReduce का समूह भाग ) पर आधारित तत्व ,

  • mapउनके दूसरे टपल भाग (समूह मानचित्र में कमी का नक्शा भाग ) के लिए समूहीकृत मूल्य ,

  • reduceमैप किए गए मान ( _+_) उन्हें संक्षेप में (समूह मानचित्र के भाग को कम करें )।


3

यहाँ मैंने क्या उपयोग किया है:

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

1
यह वास्तव में ओपी द्वारा प्रस्तावित 1 समाधान से काफी अलग नहीं है।
jwvh

2

आंद्रेज डॉयल के उत्तर में अर्धवृतों का एक बड़ा विवरण है जो आपको उपयोग करने की अनुमति देता है |+| दो मानचित्रों में शामिल होने के ऑपरेटर का करने और मिलान कुंजी के लिए मानों को योग ।

ऐसे कई तरीके हैं जिन्हें कुछ टाइपकास्ट के उदाहरण के रूप में परिभाषित किया जा सकता है, और ओपी के विपरीत आप विशेष रूप से अपनी कुंजियों का योग नहीं करना चाहते हैं। या, आप एक चौराहे के बजाय एक संघ पर काम करना चाह सकते हैं। स्कालाज अतिरिक्त कार्यों को भी जोड़ता हैMapइस उद्देश्य के लिए :

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

तुम कर सकते हो

import scalaz.Scalaz._

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

2

सबसे तेज और सरल तरीका:

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

इस तरह, तत्व के प्रत्येक को तुरंत मानचित्र में जोड़ा गया।

दूसरा ++तरीका है:

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

पहले तरीके के विपरीत, दूसरे नक्शे में प्रत्येक तत्व के लिए एक दूसरे तरीके से एक नई सूची बनाई जाएगी और पिछले नक्शे को संक्षिप्त किया जाएगा।

caseअभिव्यक्ति परोक्ष एक नया का उपयोग कर सूची बनाता है unapplyविधि।


1

मैंने ये ढूंढ निकाला...

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

टाइपकास्ट पैटर्न का उपयोग करके, हम किसी भी न्यूमेरिक प्रकार को मर्ज कर सकते हैं:

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

उपयोग:

import MapSyntax.MapOps

map1 plus map2

नक्शों का क्रम जोड़ना:

maps.reduce(_ plus _)

0

मुझे काम करने के लिए एक छोटा सा कार्य मिला है, यह मेरी छोटी लाइब्रेरी में कुछ अक्सर उपयोग की जाने वाली कार्यक्षमता के लिए है जो मानक रूप से काम नहीं करता है। यह सभी प्रकार के मानचित्रों के लिए काम करना चाहिए, परस्पर और अपरिवर्तनीय, न केवल हैशमैप

यहाँ उपयोग है

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

और यहाँ शरीर है

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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.