Java8: HashMap <X, Y> से HashMap <X, Z> स्ट्रीम / मैप-रिड्यूस / कलेक्टर का उपयोग कर


209

मुझे पता है कि कैसे - Listसे सरल जावा को "बदलना" है , अर्थात:YZ

List<String> x;
List<Integer> y = x.stream()
        .map(s -> Integer.parseInt(s))
        .collect(Collectors.toList());

अब मैं मूल रूप से एक मानचित्र के साथ ही करना चाहता हूं, अर्थात:

INPUT:
{
  "key1" -> "41",    // "41" and "42"
  "key2" -> "42      // are Strings
}

OUTPUT:
{
  "key1" -> 41,      // 41 and 42
  "key2" -> 42       // are Integers
}

समाधान String-> तक सीमित नहीं होना चाहिए IntegerListउपरोक्त उदाहरण की तरह , मैं किसी भी विधि (या निर्माता) को कॉल करना चाहूंगा।

जवाबों:


372
Map<String, String> x;
Map<String, Integer> y =
    x.entrySet().stream()
        .collect(Collectors.toMap(
            e -> e.getKey(),
            e -> Integer.parseInt(e.getValue())
        ));

यह सूची कोड जितना अच्छा नहीं है। आप कॉल Map.Entryमें नए s का निर्माण नहीं कर सकते हैं, map()इसलिए काम को collect()कॉल में मिलाया जाता है ।


59
आप बदल सकते हैं e -> e.getKey()के साथ Map.Entry::getKey। लेकिन यह स्वाद / प्रोग्रामिंग शैली की बात है।
होल्गर १

5
वास्तव में यह प्रदर्शन की बात है, आपका सुझाव लंबोदा 'शैली' से थोड़ा बेहतर है
जॉन बर्गिन

36

यहाँ Sotirios Delimanolis 'के उत्तर पर कुछ भिन्नताएँ हैं , जो कि (+1) के साथ शुरू करना बहुत अच्छा था। निम्नलिखित को धयान मे रखते हुए:

static <X, Y, Z> Map<X, Z> transform(Map<? extends X, ? extends Y> input,
                                     Function<Y, Z> function) {
    return input.keySet().stream()
        .collect(Collectors.toMap(Function.identity(),
                                  key -> function.apply(input.get(key))));
}

एक युगल यहाँ इंगित करता है। पहले जेनरिक में वाइल्डकार्ड का उपयोग होता है; यह फ़ंक्शन को कुछ अधिक लचीला बनाता है। एक वाइल्डकार्ड आवश्यक होगा यदि, उदाहरण के लिए, आप चाहते थे कि आउटपुट मैप में एक कुंजी हो जो इनपुट मैप की कुंजी का एक सुपरक्लास हो:

Map<String, String> input = new HashMap<String, String>();
input.put("string1", "42");
input.put("string2", "41");
Map<CharSequence, Integer> output = transform(input, Integer::parseInt);

(नक्शे के मूल्यों के लिए एक उदाहरण भी है, लेकिन यह वास्तव में वंचित है, और मैं मानता हूं कि वाई के लिए बाध्य वाइल्डकार्ड केवल किनारे के मामलों में मदद करता है।)

एक दूसरा बिंदु यह है कि इनपुट मानचित्र पर धारा को चलाने के बजाय entrySet, मैंने इसे ऊपर चलाया keySet। यह कोड को थोड़ा क्लीनर बनाता है, मुझे लगता है, नक्शे की प्रविष्टि के बजाय नक्शे से बाहर मूल्यों को लाने की कीमत पर। संयोग से, मैं शुरू key -> keyमें पहले तर्क के रूप में था toMap()और यह किसी कारण के लिए एक प्रकार की अनुमान त्रुटि के साथ विफल रहा। इसे बदलने के लिए (X key) -> keyकाम किया, जैसा कि किया था Function.identity()

फिर भी एक और भिन्नता इस प्रकार है:

static <X, Y, Z> Map<X, Z> transform1(Map<? extends X, ? extends Y> input,
                                      Function<Y, Z> function) {
    Map<X, Z> result = new HashMap<>();
    input.forEach((k, v) -> result.put(k, function.apply(v)));
    return result;
}

यह Map.forEach()धाराओं के बजाय उपयोग करता है। यह और भी सरल है, मुझे लगता है, क्योंकि यह कलेक्टरों के साथ फैलाव करता है, जो कि नक्शे के साथ उपयोग करने के लिए कुछ अनाड़ी हैं। कारण यह है कि Map.forEach()कुंजी और मूल्य को अलग-अलग मापदंडों के रूप में दिया जाता है, जबकि स्ट्रीम का केवल एक मूल्य होता है - और आपको यह चुनना होगा कि कुंजी या मानचित्र प्रविष्टि को उस मान के रूप में उपयोग करना है या नहीं। माइनस साइड पर, यह अन्य दृष्टिकोणों की समृद्ध, सुव्यवस्थित अच्छाता का अभाव है। :-)


11
Function.identity()अच्छा लग सकता है, लेकिन चूंकि पहले समाधान के लिए हर प्रविष्टि के लिए मानचित्र / हैश लुकअप की आवश्यकता होती है, जबकि अन्य सभी समाधान नहीं होते हैं, मैं इसकी सिफारिश नहीं करूंगा।
होल्गर १

13

ऐसा एक जेनेरिक समाधान

public static <X, Y, Z> Map<X, Z> transform(Map<X, Y> input,
        Function<Y, Z> function) {
    return input
            .entrySet()
            .stream()
            .collect(
                    Collectors.toMap((entry) -> entry.getKey(),
                            (entry) -> function.apply(entry.getValue())));
}

उदाहरण

Map<String, String> input = new HashMap<String, String>();
input.put("string1", "42");
input.put("string2", "41");
Map<String, Integer> output = transform(input,
            (val) -> Integer.parseInt(val));

जेनरिक का उपयोग कर अच्छा तरीका। मुझे लगता है कि इसमें थोड़ा सुधार किया जा सकता है - मेरा उत्तर देखें।
स्टुअर्ट मार्क्स 6

13

अमरूद की फंक्शन Maps.transformValuesवही है जो आप देख रहे हैं, और यह अच्छी तरह से लंबोदर भावों के साथ काम करता है:

Maps.transformValues(originalMap, val -> ...)

मुझे यह दृष्टिकोण पसंद है, लेकिन सावधान रहें कि इसे java.util पास न करें। चूंकि यह com.google.common.base.Function की अपेक्षा करता है, इसलिए ग्रहण एक अस्वाभाविक त्रुटि देता है - यह कहता है कि फ़ंक्शन फ़ंक्शन के लिए लागू नहीं है, जो भ्रामक हो सकता है: "विधि ट्रांसफ़ॉर्मवैल्यूज़ (मैप <के, वी 1>, फ़ंक्शन <? सुपर V1] , V2>) प्रकार में तर्क तर्कों के लिए लागू नहीं होते हैं (मानचित्र <फू, बार>, फ़ंक्शन <बार, बाज>) "
mskfisher

यदि आपको पास होना चाहिए java.util.Function, तो आपके पास दो विकल्प हैं। 1. जावा प्रकार की अनुमान लगाने की अनुमति देने के लिए लैम्बडा का उपयोग करके समस्या से बचें। 2. javaFunction की तरह एक विधि संदर्भ का उपयोग करें :: एक नए लैम्ब्डा का उत्पादन करने के लिए लागू करें जो कि प्रकार का अनुमान लगा सकता है।
जो

10

क्या यह बिल्कुल 100% कार्यात्मक और धाराप्रवाह होना चाहिए? यदि नहीं, तो इस बारे में कैसे, जो इसके बारे में जितना छोटा है:

Map<String, Integer> output = new HashMap<>();
input.forEach((k, v) -> output.put(k, Integer.valueOf(v));

( यदि आप साइड-इफेक्ट के साथ धाराओं के संयोजन की शर्म और अपराध बोध के साथ रह सकते हैं )


5

मेरा स्ट्रीमटेक्स लाइब्रेरी जो मानक स्ट्रीम एपीआई को बढ़ाता है एक EntryStreamवर्ग प्रदान करता है जो मानचित्रों को बदलने के लिए बेहतर है:

Map<String, Integer> output = EntryStream.of(input).mapValues(Integer::valueOf).toMap();

4

एक उद्देश्य जो सीखने के उद्देश्य के लिए हमेशा मौजूद रहता है, वह है कस्टम कलेक्टर (कलेक्टर) के माध्यम से अपने कस्टम कलेक्टर का निर्माण करना, हालांकि () JDK कलेक्टर यहाँ succinct (+1 यहाँ ) है।

Map<String,Integer> newMap = givenMap.
                entrySet().
                stream().collect(Collector.of
               ( ()-> new HashMap<String,Integer>(),
                       (mutableMap,entryItem)-> mutableMap.put(entryItem.getKey(),Integer.parseInt(entryItem.getValue())),
                       (map1,map2)->{ map1.putAll(map2); return map1;}
               ));

मैंने इस कस्टम कलेक्टर के साथ एक आधार के रूप में शुरुआत की और इसे जोड़ना चाहता था, कम से कम जब धारा () के बजाय समानांतर स्ट्र्रीम () का उपयोग कर रहा हो, तो बाइनरीऑपरेटर को कुछ अधिक फिर से लिखा जाना चाहिए map2.entrySet().forEach(entry -> { if (map1.containsKey(entry.getKey())) { map1.get(entry.getKey()).merge(entry.getValue()); } else { map1.put(entry.getKey(),entry.getValue()); } }); return map1या कम करने पर मूल्यों को खो दिया जाएगा।
user691154

3

यदि आप तृतीय पक्ष पुस्तकालयों का उपयोग करने में कोई आपत्ति नहीं करते हैं, तो मेरी साइक्लॉप्स-प्रतिक्रिया एफबीआई में मानचित्र सहित सभी JDK संग्रह प्रकारों के लिए एक्सटेंशन हैं । हम 'मैप' ऑपरेटर (मानचित्र में मानों पर डिफ़ॉल्ट रूप से कार्य करके) का उपयोग करके सीधे मानचित्र बदल सकते हैं।

   MapX<String,Integer> y = MapX.fromMap(HashMaps.of("hello","1"))
                                .map(Integer::parseInt);

bimap का उपयोग एक ही समय में कुंजियों और मूल्यों को बदलने के लिए किया जा सकता है

  MapX<String,Integer> y = MapX.fromMap(HashMaps.of("hello","1"))
                               .bimap(this::newKey,Integer::parseInt);

0

घोषणात्मक और सरल समाधान होगा:

yourMutableMap.replaceAll ((कुंजी, वैल) -> रिटर्न_वायु_ऑफ_बी_यूर_कुलेशन); नायब। अपने नक्शे राज्य को संशोधित करने के बारे में जागरूक रहें। तो यह वह नहीं हो सकता जो आप चाहते हैं।

चीयर्स: http://www.deadcoderising.com/2017-02-14-java-8-declarative-ways-of-modifying-a-map-use-compute-merge-and-replace/

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