2015-08-21 7 views
17

मैं निम्नलिखित संग्रह प्रकार है:मैं जावा 8 स्ट्रीम के साथ कार्टेशियन उत्पाद कैसे बना सकता हूं?

Map<String, Collection<String>> map; 

मैं प्रत्येक कुंजी के लिए संग्रह में एक भी मूल्य से map.size() में से प्रत्येक के अद्वितीय संयोजन बनाने के लिए करना चाहते हैं।

उदाहरण के लिए मान लीजिए नक्शा लगता है कि निम्नलिखित:

A, {a1, a2, a3, ..., an} 
B, {b1, b2, b3, ..., bn} 
C, {c1, c2, c3, ..., cn} 

परिणाम मैं प्राप्त करना चाहते हैं होगा एक List<Set<String>> परिणाम, के लिए (आदेश महत्वपूर्ण नहीं है समान लग रही है, यह सिर्फ एक 'पूरा होने की जरूरत है 'सभी संभव संयोजनों से मिलकर परिणाम):

{a1, b1, c1}, 
{a1, b1, c2}, 
{a1, b1, c3}, 
{a1, b2, c1}, 
{a1, b2, c2}, 
{a1, b2, c3}, 
... 
{a2, b1, c1}, 
{a2, b1, c2}, 
... 
{a3, b1, c1}, 
{a3, b1, c2}, 
... 
{an, bn, cn} 

यह मूल रूप से एक गिनती समस्या है, लेकिन मैं अगर एक समाधान जावा 8 धाराओं का इस्तेमाल करके संभव है देखना चाहेंगे।

+1

आप कुंजी के बारे में परवाह मत करो मैं नक्शा? – durron597

+0

तकनीकी रूप से, परिणाम को कई 'क्लास परिणाम इन्ट्री {स्ट्रिंग कुंजी, मान;}' के रूप में परिभाषित किया गया है, जैसे कि अंतिम परिणाम प्रकार 'सूची <सेट >' प्रकार है, इसलिए हाँ, मुझे चाबियों की परवाह है, हालांकि, मुझे लगा, अगर मुझे क्रमपरिवर्तन की मूल बातें मिलती हैं तो मैं पता लगा सकता हूं कि मैपिंग में कुंजी कैसे काम करें। –

+1

उस मानचित्र कुंजी में आपको कोई समस्या नहीं है, जब तक कि यह 'सॉर्टेड मैप' न हो। इसका मतलब है कि आप नहीं जानते कि आपका उत्तर '{a1, b1, c1}' या '{c1, a1, b1}' या अन्य 4 संभावनाओं से शुरू होगा या नहीं। सवाल अभी भी दिलचस्प है, लेकिन चाबियों के आदेश के बिना, यह कोई सवाल नहीं है जो वास्तविक जीवन में उपयोगी होगा। 'सूची <संग्रह >' का उपयोग करके इनपुट के रूप में अधिक समझ हो सकती है। – ajb

उत्तर

14

आप रिकर्सिव flatMap श्रृंखला का उपयोग कर इसे हल कर सकते हैं।

सबसे पहले हमें मानचित्र मूल्यों से आगे बढ़ने की आवश्यकता है, तो उन्हें ArrayList पर कॉपी करना बेहतर है (यह गहरी प्रति नहीं है, आपके मामले में यह केवल 0 तत्वों के ArrayList है, इसलिए अतिरिक्त मेमोरी उपयोग है कम)।

दूसरा, पहले देखी तत्वों का एक उपसर्ग बनाए रखने के लिए, के एक सहायक अपरिवर्तनीय Prefix वर्ग बना सकते हैं:

List<String> list = new Prefix<>(new Prefix<>(new Prefix<>(null, "a"), "b"), "c") 
          .addTo(new ArrayList<>()); // [a, b, c]; 
:

private static class Prefix<T> { 
    final T value; 
    final Prefix<T> parent; 

    Prefix(Prefix<T> parent, T value) { 
     this.parent = parent; 
     this.value = value; 
    } 

    // put the whole prefix into given collection 
    <C extends Collection<T>> C addTo(C collection) { 
     if (parent != null) 
      parent.addTo(collection); 
     collection.add(value); 
     return collection; 
    } 
} 

यह बहुत ही सरल अपरिवर्तनीय लिंक्ड सूची जो इस तरह इस्तेमाल किया जा सकता है

इसके बाद, के आंतरिक विधि बना सकते हैं जो चेन flatMaps:

private static <T, C extends Collection<T>> Stream<C> comb(
     List<? extends Collection<T>> values, int offset, Prefix<T> prefix, 
     Supplier<C> supplier) { 
    if (offset == values.size() - 1) 
     return values.get(offset).stream() 
        .map(e -> new Prefix<>(prefix, e).addTo(supplier.get())); 
    return values.get(offset).stream() 
      .flatMap(e -> comb(values, offset + 1, new Prefix<>(prefix, e), supplier)); 
} 

रिकर्सन की तरह दिखता है, लेकिन यह अधिक जटिल है: यह खुद को सीधे कॉल नहीं करता है, लेकिन बाहरी विधि को कॉल करने वाले लैम्ब्डा को पास करता है। पैरामीटर:

  • मान: मूल मूल्यों के List (new ArrayList<>(map.values) अपने मामले में)।
  • ऑफसेट: वर्तमान में इस सूची के भीतर ऑफसेट
  • उपसर्ग: लंबाई की वर्तमान उपसर्ग ऑफसेट (या null अगर offset == 0)। इसमें वर्तमान में list.get(0), list.get(1) संग्रह list.get(offset-1) संग्रह से चयनित तत्व शामिल हैं।
  • आपूर्तिकर्ता: परिणामी संग्रह बनाने के लिए फैक्ट्री विधि।

जब हम मान सूची (offset == values.size() - 1) के अंत तक पहुँच है, हम आपूर्तिकर्ता का उपयोग कर अंतिम संयोजन के मूल्यों से पिछले संग्रह के तत्वों को मैप करें। अन्यथा हम flatMap का उपयोग करते हैं जो प्रत्येक मध्यवर्ती तत्व उपसर्ग को बढ़ाता है और अगले ऑफसेट के लिए comb विधि को फिर से कॉल करता है।फिर क्रम बनाए रखने के

Map<String, Collection<String>> map = new LinkedHashMap<>(); // to preserve the order 
map.put("A", Arrays.asList("a1", "a2", "a3", "a4")); 
map.put("B", Arrays.asList("b1", "b2", "b3")); 
map.put("C", Arrays.asList("c1", "c2")); 

ofCombinations(map.values(), LinkedHashSet::new).forEach(System.out::println); 

हम LinkedHashSet लिए अलग-अलग संयोजन इकट्ठा:

public static <T, C extends Collection<T>> Stream<C> ofCombinations(
     Collection<? extends Collection<T>> values, Supplier<C> supplier) { 
    if (values.isEmpty()) 
     return Stream.empty(); 
    return comb(new ArrayList<>(values), 0, null, supplier); 
} 

एक के उपयोग का उदाहरण:

अंत में यहाँ सार्वजनिक विधि इस सुविधा का उपयोग करने के लिए है। आप इसके बजाय किसी अन्य संग्रह का उपयोग कर सकते हैं (उदा। ArrayList::new)।

+4

निष्कर्ष: यह इतना आसान नहीं है) –

+1

@SashaSalauyou मनमाने ढंग से संग्रह प्रकारों के सामान्यीकरण को छोड़कर यह बहुत आसान हो सकता है। – Marco13

2

यहां एक और समाधान है, जो Streams से टैगिर के उदाहरण के रूप में कई सुविधाओं का उपयोग नहीं करता है; लेकिन मेरा मानना ​​है कि यह सीधी-सपाट होने के लिए:

public class Permutations { 

    transient List<Collection<String>> perms; 

    public List<Collection<String>> list(Map<String, Collection<String>> map) { 

     SortedMap<String, Collection<String>> sortedMap = new TreeMap<>(); 
     sortedMap.putAll(map); 

     sortedMap.values().forEach((v) -> perms = expand(perms, v)); 

     return perms; 
    } 

    private List<Collection<String>> expand(List<Collection<String>> list, Collection<String> elements) { 

     List<Collection<String>> newList = new LinkedList<>(); 

     if (list == null) { 
      elements.forEach((e) -> { 
       SortedSet<String> set = new TreeSet<>(); 
       set.add(e); 
       newList.add(set); 
      }); 
     } else { 
      list.forEach((set) -> 
       elements.forEach((e) -> { 
        SortedSet<String> newSet = new TreeSet<>(); 
        newSet.addAll(set); 
        newSet.add(e); 
        newList.add(newSet); 
       })); 
     } 

     return newList; 
    } 
} 

आप Sorted उपसर्ग हटा सकते हैं अगर आप तत्वों का आदेश देने में कोई दिलचस्पी नहीं कर रहे हैं; हालांकि, मुझे लगता है कि सबकुछ हल होने पर डीबग करना आसान है।

उपयोग:

Permutations p = new Permutations(); 
List<Collection<String>> plist = p.list(map); 
plist.forEach((s) -> System.out.println(s)); 

का आनंद लें!

+2

ध्यान दें कि आपका समाधान वास्तव में शून्य स्ट्रीम एपीआई सुविधाओं का उपयोग करता है ('संग्रह .forEach' स्ट्रीम एपीआई का हिस्सा नहीं है)। आप '.forEach' को पुराने पुराने 'इन-इन' लूप के साथ प्रतिस्थापित कर सकते हैं और आपका कोड जावा 5-संगत होगा। यह भी ध्यान रखें कि आप स्मृति में सभी संयोजनों को स्टोर करते हैं। हालांकि यह ओपी के लिए ठीक लगता है, यह बड़े इनपुट के साथ समस्याग्रस्त हो सकता है। अंत में इसे समानांतर करने का कोई आसान तरीका नहीं है। –

7

एक समाधान जो मुख्य रूप से सूचियों पर काम करता है, जिससे चीजों को बहुत आसान बना दिया जाता है। यह flatMap में एक पुनरावर्ती कॉल करता है, तत्व है कि पहले से ही संयुक्त किया गया है, और तत्वों है कि अभी भी याद कर रहे हैं के संग्रह का ट्रैक रखने, और सूचियों की एक धारा के रूप में इस नेस्टेड पुनरावर्ती निर्माण के परिणाम प्रदान करता है:

import java.util.*; 
import java.util.stream.Stream; 

public class CartesianProduct { 

    public static void main(String[] args) { 
     Map<String, Collection<String>> map = 
      new LinkedHashMap<String, Collection<String>>(); 
     map.put("A", Arrays.asList("a1", "a2", "a3", "a4")); 
     map.put("B", Arrays.asList("b1", "b2", "b3")); 
     map.put("C", Arrays.asList("c1", "c2")); 
     ofCombinations(map.values()).forEach(System.out::println); 
    } 

    public static <T> Stream<List<T>> ofCombinations(
     Collection<? extends Collection<T>> collections) { 
     return ofCombinations(
      new ArrayList<Collection<T>>(collections), 
      Collections.emptyList());   
    }  

    private static <T> Stream<List<T>> ofCombinations(
     List<? extends Collection<T>> collections, List<T> current) { 
     return collections.isEmpty() ? Stream.of(current) : 
      collections.get(0).stream().flatMap(e -> 
      { 
       List<T> list = new ArrayList<T>(current); 
       list.add(e); 
       return ofCombinations(
        collections.subList(1, collections.size()), list); 
      }); 
    } 
} 
+0

यह मेरा मूल विचार था, लेकिन इस तरह के समाधान में अधिक प्रतिलिपि शामिल है, इस प्रकार कम प्रभावी ... –

+2

@TagirValeev वे दोनों समान रूप से प्रभावी हैं। कौन सा * कुशल * कहना मुश्किल है। लेकिन मुझे यह भी ध्यान में था: स्ट्रीम आलसी हैं, और "अगले" तत्व को प्राप्त करने में कुछ जटिल रिकर्सिव कॉल शामिल हो सकते हैं जिनमें 'फ्लैटमैप' शामिल है, इसलिए एक समर्पित बेंचमार्क यहां दिलचस्प हो सकता है। इसके अलावा, मुझे लगता है कि सामान्य रूप से स्ट्रीम यहां सबसे अच्छा समाधान नहीं है (लेकिन यही वह पूछता है)। किसी को [इसे हल करने के लिए Iterable] पर विचार करना चाहिए (https://github.com/javagl/Combinatorics/blob/master/src/main/java/de/javagl/utils/math/combinatorics/MixedRangeCombinationIterable.java) ... – Marco13

0

foreach के साथ जावा 8 में एक उपभोक्ता समारोह क्लास, एक सूची और एक foreach

public void tester(){ 

     String[] strs1 = {"2","4","9"}; 
     String[] strs2 = {"9","0","5"}; 

     //Final output is {"29", "49, 99", "20", "40", "90", "25", "45", "95"} 
     List<String> result = new ArrayList<>(); 
     Consumer<String> consumer = (String str) -> result.addAll(Arrays.stream(strs1).map(s -> s+str).collect(Collectors.toList())); 
     Arrays.stream(strs2).forEach(consumer); 

     System.out.println(result); 

} 
2

कार्तीय उत्पाद का उपयोग करें:

List<String> listA = new ArrayList<>(); 
listA.add("0"); 
listA.add("1"); 
List<String> listB = new ArrayList<>(); 
listB.add("a"); 
listB.add("b"); 

List<String> cartesianProduct = new ArrayList<>(); 
listA.forEach(a -> listB.forEach(b -> cartesianProduct.add(a + b))); 

cartesianProduct.forEach(System.out::println); 
//Output : 0a 0b 1a 1b 
+0

क्या आप एक पूरा उत्तर पोस्ट कर सकते हैं? सूची ए और सूची बी, सामग्री, और आउटपुट की घोषणा की तरह? – efekctive

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