2015-09-21 7 views
13

मैं के साथ वस्तुओं की एक सूची है, जिनमें कई दोहराया और कुछ क्षेत्रों की जरूरत है जिन्हें मर्ज करने की। मैं इसे केवल जावा 8 स्ट्रीम का उपयोग करके अद्वितीय वस्तुओं की सूची में कम करना चाहता हूं (मुझे पता है कि पुराने-स्कूल माध्यमों के माध्यम से इसे कैसे करना है, लेकिन यह एक प्रयोग है।)समूह और कम वस्तुओं की सूची

यह मेरे पास अभी है। मैं वास्तव में यह पसंद नहीं है क्योंकि नक्शा निर्माण बाहरी लगता है और मूल्यों() संग्रह समर्थन नक्शा के एक दृश्य है, और आप एक नया ArrayList<>(...) में लपेट के लिए एक अधिक विशिष्ट संग्रह प्राप्त करने की आवश्यकता है। क्या कोई बेहतर दृष्टिकोण है, शायद अधिक सामान्य कमी संचालन का उपयोग कर?

@Test 
public void reduce() { 
    Collection<Foo> foos = Stream.of("foo", "bar", "baz") 
        .flatMap(this::getfoos) 
        .collect(Collectors.toMap(f -> f.name, f -> f, (l, r) -> { 
         l.ids.addAll(r.ids); 
         return l; 
        })).values(); 

    assertEquals(3, foos.size()); 
    foos.forEach(f -> assertEquals(10, f.ids.size())); 
} 

private Stream<Foo> getfoos(String n) { 
    return IntStream.range(0,10).mapToObj(i -> new Foo(n, i)); 
} 

public static class Foo { 
    private String name; 
    private List<Integer> ids = new ArrayList<>(); 

    public Foo(String n, int i) { 
     name = n; 
     ids.add(i); 
    } 
} 
+2

क्या मध्यवर्ती मानचित्र का उपयोग किए बिना इस "पुरानी स्कूल" (पारंपरिक रूप से, लैम्ब्डा/धाराओं के बिना) को कार्यान्वित करना संभव है? मुझे लगता है कि चूंकि डुप्लीकेट संभावित रूप से इनपुट में कहीं भी हो सकते हैं, इसलिए सभी को तब तक बफर किया जाना चाहिए जब तक कि सभी इनपुट संसाधित नहीं हो जाते। –

उत्तर

6

आप समूहीकरण तोड़ने के लिए और कदम को कम करने के लिए, आप क्लीनर कुछ प्राप्त कर सकते हैं: जैसा कि पहले ही

public Foo(String n, List<Integer> ids) { 
    this.name = n; 
    this.ids.addAll(ids); 
} 

public static Foo merge(Foo src, Foo dest) { 
    List<Integer> merged = new ArrayList<>(); 
    merged.addAll(src.ids); 
    merged.addAll(dest.ids); 
    return new Foo(src.name, merged); 
} 
+1

यह लगभग एक ही चीज की मात्रा है - केवल आप ही नई 'Foo' ऑब्जेक्ट्स बनाते हैं, और आपकी सूची' Foo' की सूची के बजाय 'वैकल्पिक ' की एक सूची है, जो बिल्कुल साफ़ नहीं है। – RealSkeptic

+0

क्या आप नये फू बनाने के बजाए dest foo से src में सिर्फ ids जोड़ नहीं सकते थे? – ryber

+3

@ryber यकीन है, लेकिन वास्तविक दुनिया परिदृश्य में जो आसानी से अप्रत्याशित मुद्दों को बना सकता है, खासकर यदि आपकी कमी समानांतर में चल रही है। मैं आपके स्ट्रीमिंग परिचालन में उत्परिवर्तन को कम करने की सिफारिश करता हूं। देखें: https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html#Reduction। –

2

:

Stream<Foo> input = Stream.of("foo", "bar", "baz").flatMap(this::getfoos); 

Map<String, Optional<Foo>> collect = input.collect(Collectors.groupingBy(f -> f.name, Collectors.reducing(Foo::merge))); 

Collection<Optional<Foo>> collected = collect.values(); 

यह आपके Foo कक्षा में कुछ सुविधा तरीकों मान लिया गया है टिप्पणियों में बताया गया है, जब आप अद्वितीय वस्तुओं की पहचान करना चाहते हैं तो मानचित्र का उपयोग करने के लिए एक बहुत ही प्राकृतिक चीज है। यदि आपको केवल अनन्य वस्तुओं को खोजने की ज़रूरत है, तो आप Stream::distinct विधि का उपयोग कर सकते हैं। इस विधि तथ्य वहाँ एक नक्शा शामिल है कि खाल, लेकिन जाहिरा तौर पर यह के रूप में this question से संकेत दिया है कि पता चलता है कि आप एक hashCode विधि को लागू करना चाहिए या distinct ठीक से व्यवहार नहीं कर सकते हैं आंतरिक रूप से नक्शे का उपयोग करता है,।

distinct विधि के मामले में, जहां कोई विलय आवश्यक नहीं है, सभी इनपुट संसाधित होने से पहले कुछ परिणामों को वापस करना संभव है। आपके मामले में, जब तक कि आप इस इनपुट में उल्लिखित इनपुट के बारे में अतिरिक्त धारणाएं नहीं बना सकते हैं, तो आपको कोई भी परिणाम वापस करने से पहले सभी इनपुट को संसाधित करने की आवश्यकता है। इस प्रकार यह उत्तर एक मानचित्र का उपयोग करता है।

यह आसान हालांकि, नक्शा के मूल्यों को संसाधित करने और उसे फिर से चालू करने के लिए एक ArrayList में धाराओं का उपयोग करने के लिए पर्याप्त है। मुझे लगता है कि इस उत्तर में, और साथ ही एक तरह से एक Optional<Foo> की उपस्थिति है, जो अन्य उत्तर में से एक में दिखाई देता है से बचने के लिए उपलब्ध कराने के दिखा।

public void reduce() { 
    ArrayList<Foo> foos = Stream.of("foo", "bar", "baz").flatMap(this::getfoos) 
      .collect(Collectors.collectingAndThen(Collectors.groupingBy(f -> f.name, 
      Collectors.reducing(Foo.identity(), Foo::merge)), 
      map -> map.values().stream(). 
       collect(Collectors.toCollection(ArrayList::new)))); 

    assertEquals(3, foos.size()); 
    foos.forEach(f -> assertEquals(10, f.ids.size())); 
} 

private Stream<Foo> getfoos(String n) { 
    return IntStream.range(0, 10).mapToObj(i -> new Foo(n, i)); 
} 

public static class Foo { 
    private String name; 
    private List<Integer> ids = new ArrayList<>(); 

    private static final Foo BASE_FOO = new Foo("", 0); 

    public static Foo identity() { 
     return BASE_FOO; 
    } 

    // use only if side effects to the argument objects are okay 
    public static Foo merge(Foo fooOne, Foo fooTwo) { 
     if (fooOne == BASE_FOO) { 
      return fooTwo; 
     } else if (fooTwo == BASE_FOO) { 
      return fooOne; 
     } 
     fooOne.ids.addAll(fooTwo.ids); 
     return fooOne; 
    } 

    public Foo(String n, int i) { 
     name = n; 
     ids.add(i); 
    } 
} 
+1

यह सब 'map.values ​​()। स्ट्रीम()। संग्रह (blahblah)' क्यों? अच्छा पुराना 'मानचित्र -> नया ऐरेलिस्ट <> (map.values ​​())' सरल और तेज़ होगा। –

+0

@ टागीर वैलेव: यदि परिणाम पर लागू एकमात्र ऑपरेशन 'आकार() 'और' के लिए() 'है, तो' map.values ​​() 'संग्रह को एक नई सूची में कॉपी करने का कोई कारण नहीं है। – Holger

1

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

दुर्भाग्य से वहाँ कोई स्ट्रीम एपीआई विधि है जो आप आसानी से और प्रभावी रूप से इस तरह के बात करने के लिए क्या नहीं प्रतीत होती।

public static List<Foo> withCollector(Stream<Foo> stream) { 
    return stream.collect(Collector.<Foo, List<Foo>>of(ArrayList::new, 
      (list, t) -> { 
       Foo f; 
       if(list.isEmpty() || !(f = list.get(list.size()-1)).name.equals(t.name)) 
        list.add(t); 
       else 
        f.ids.addAll(t.ids); 
      }, 
      (l1, l2) -> { 
       if(l1.isEmpty()) 
        return l2; 
       if(l2.isEmpty()) 
        return l1; 
       if(l1.get(l1.size()-1).name.equals(l2.get(0).name)) { 
        l1.get(l1.size()-1).ids.addAll(l2.get(0).ids); 
        l1.addAll(l2.subList(1, l2.size())); 
       } else { 
        l1.addAll(l2); 
       } 
       return l1; 
      })); 
} 

मेरे परीक्षणों से पता चलता है कि इस कलेक्टर मैप करने के लिए, दोनों अनुक्रमिक और समानांतर मोड (नकली नाम की औसत संख्या के आधार पर 2 गुना तक) इकट्ठा करने की तुलना में हमेशा तेजी से होता है: एक संभव समाधान इस तरह कस्टम कलेक्टर लिखना है ।

public static List<Foo> withStreamEx(Stream<Foo> stream) { 
    return StreamEx.of(stream) 
      .collapse((l, r) -> l.name.equals(r.name), (l, r) -> { 
       l.ids.addAll(r.ids); 
       return l; 
      }).toList(); 
} 

इस विधि दो तर्क स्वीकार करता है:: एक BiPredicate जो दो आसन्न तत्वों के लिए लागू किया जाता है और वापस आ जाएगी

एक और दृष्टिकोण जो collapse सहित "आंशिक कमी" तरीकों में से एक गुच्छा प्रदान करता है मेरी StreamEx लाइब्रेरी का उपयोग करने के लिए है सत्य अगर तत्वों को विलय किया जाना चाहिए और BinaryOperator जो विलय करता है। यह समाधान कस्टम कलेक्टर की तुलना में अनुक्रमिक मोड में थोड़ा धीमा है (समानांतर में परिणाम बहुत समान हैं), लेकिन यह अभी भी toMap समाधान से काफी तेज़ है और यह आसान है और collapse एक मध्यवर्ती ऑपरेशन है, इसलिए आप एकत्र कर सकते हैं एक अन्य तरीके से।

फिर ये दोनों समाधान केवल तभी काम करते हैं जब एक ही नाम के साथ फूस निकट होने के लिए जाना जाता है। फू नाम द्वारा इनपुट स्ट्रीम को सॉर्ट करना, फिर इन समाधानों का उपयोग करना एक बुरा विचार है, क्योंकि सॉर्टिंग प्रदर्शन को कम कर देगा जिससे इसे toMap समाधान से धीमा कर दिया जा सके।

1

जैसा कि पहले से ही दूसरों द्वारा इंगित किया गया है, एक मध्यवर्ती Map अपरिहार्य है, क्योंकि वस्तुओं को मर्ज करने के लिए यह तरीका है। इसके अलावा, आपको कमी के दौरान स्रोत डेटा को संशोधित नहीं करना चाहिए।

List<Foo> foos = Stream.of("foo", "bar", "baz") 
       .flatMap(n->IntStream.range(0,10).mapToObj(i -> new Foo(n, i))) 

       .collect(collectingAndThen(groupingBy(f -> f.name), 
        m->m.entrySet().stream().map(e->new Foo(e.getKey(), 
         e.getValue().stream().flatMap(f->f.ids.stream()).collect(toList()))) 
        .collect(toList()))); 

मतलब यह है कि आप अपने Foo वर्ग के लिए एक निर्माता

public Foo(String n, List<Integer> l) { 
     name = n; 
     ids=l; 
    } 

जोड़ने के लिए, के रूप में यह होना चाहिए अगर Foo वास्तव में है:

फिर भी, आप एक से अधिक Foo उदाहरणों बनाए बिना दोनों प्राप्त कर सकते हैं आईडी की एक सूची रखने में सक्षम होना चाहिए। एक साइड नोट के रूप में, एक प्रकार के रूप में जो एक आइटम के रूप में कार्य करता है साथ ही मर्ज किए गए परिणामों के लिए एक कंटेनर भी मेरे लिए अप्राकृतिक लगता है। यही कारण है कि कोड इतना जटिल क्यों हो जाता है।

यदि स्रोत आइटमों में id था, तो groupingBy(f -> f.name, mapping(f -> id, toList()) जैसे कुछ का उपयोग करके, मर्ज किए गए आइटमों में (String, List<Integer>) की प्रविष्टियों को मैप करने के बाद पर्याप्त था।

चूंकि यह मामला नहीं है और जावा 8 में flatMapping कलेक्टर की कमी है, तो फ्लैटमैपिंग चरण दूसरे चरण में स्थानांतरित हो गया है, जिससे यह अधिक जटिल दिखता है।

लेकिन दोनों मामलों में, दूसरा चरण अप्रचलित नहीं है क्योंकि यह परिणाम है जहां परिणाम आइटम वास्तव में बनाए जाते हैं और मानचित्र को वांछित सूची प्रकार में परिवर्तित कर सकते हैं।

+0

अपरिवर्तनीय वस्तुएं निश्चित रूप से अच्छी हैं, हालांकि यह ध्यान दिया जाना चाहिए कि वर्तमान समाधान ओपी के कोड की तुलना में दोगुनी धीमी है। 'फ्लैटमैपिंग' कलेक्टर का उपयोग करना बेहतर होगा ... –

+1

@ टगीर वैलेव: इस मामले में, यह वस्तुओं को अपरिवर्तनीय या नहीं होने के बारे में नहीं है। यह केवल उस कमी के बारे में स्रोत वस्तुओं को संशोधित नहीं करना चाहिए। मुझे लगता है, आप कल्पना कर सकते हैं कि स्रोत ऑब्जेक्ट्स का अभी भी उपयोग होने पर यह कैसे बैकफायर कर सकता है ... – Holger

संबंधित मुद्दे