क्या आप एक धारा को दो धाराओं में विभाजित कर सकते हैं?


146

मेरे पास जावा 8 स्ट्रीम द्वारा दर्शाया गया डेटा सेट है:

Stream<T> stream = ...;

मैं देख सकता हूँ कि एक यादृच्छिक सबसेट पाने के लिए इसे कैसे फ़िल्टर किया जाए - उदाहरण के लिए

Random r = new Random();
PrimitiveIterator.OfInt coin = r.ints(0, 2).iterator();   
Stream<T> heads = stream.filter((x) -> (coin.nextInt() == 0));

मैं यह भी देख सकता हूं कि मैं इस स्ट्रीम को कैसे प्राप्त कर सकता हूं, उदाहरण के लिए, डेटा सेट के दो यादृच्छिक हिस्सों का प्रतिनिधित्व करने वाली दो सूचियां, और फिर उन धाराओं को वापस चालू करें। लेकिन, क्या शुरुआती एक से दो धाराओं को उत्पन्न करने का एक सीधा तरीका है? कुछ इस तरह

(heads, tails) = stream.[some kind of split based on filter]

किसी भी जानकारी के लिए धन्यवाद।


मार्क का जवाब लुई के जवाब से काफी मददगार है लेकिन मुझे कहना होगा कि लुई मूल सवाल से ज्यादा संबंधित है। प्रश्न को मध्यवर्ती रूपांतरण के बिनाStream कई Streamएस में बदलने की संभावना पर ध्यान केंद्रित किया गया है , हालांकि मुझे लगता है कि जो लोग इस सवाल पर पहुंचे हैं, वे वास्तव में इस तरह की बाधा की परवाह किए बिना हासिल करने का रास्ता देख रहे हैं, जो मार्क का जवाब है। यह इस तथ्य के कारण हो सकता है कि शीर्षक में प्रश्न विवरण में ऐसा नहीं है
देविल्लत

जवाबों:


9

बिल्कुल नहीं। आप Streamएक में से दो एस नहीं प्राप्त कर सकते हैं ; इसका कोई मतलब नहीं है - आप एक ही समय में दूसरे को उत्पन्न करने की आवश्यकता के बिना एक से अधिक कैसे पुनरावृति करेंगे? एक धारा को केवल एक बार ही संचालित किया जा सकता है।

हालाँकि, यदि आप उन्हें सूची या किसी चीज़ में डंप करना चाहते हैं, तो आप कर सकते हैं

stream.forEach((x) -> ((x == 0) ? heads : tails).add(x));

65
यह समझ में क्यों नहीं आता है? चूंकि एक स्ट्रीम एक पाइपलाइन है, इसलिए कोई कारण नहीं है कि यह मूल धारा के दो उत्पादकों को नहीं बना सकता है, मैं इसे एक कलेक्टर द्वारा नियंत्रित किया जा सकता है जो दो धाराएं प्रदान करता है।
ब्रेट रायन

36
धागा सुरक्षित नहीं। खराब सलाह एक संग्रह में सीधे जोड़ने की कोशिश कर रही है, यही कारण है कि हमारे पास stream.collect(...)पूर्वनिर्धारित धागा-तिजोरी के साथ है Collectors, यह गैर-थ्रेड-सुरक्षित संग्रह पर भी काम करता है (कोई सिंक्रनाइज़ लॉक विवाद के साथ)। @MarkJeronimus द्वारा सर्वश्रेष्ठ उत्तर।
योयो

1
@ जौड यह थ्रेड-सेफ है अगर हेड्स और टेल्स थ्रेड-सेफ हैं। इसके अतिरिक्त, गैर-समानांतर धाराओं के उपयोग को मानते हुए, केवल आदेश की गारंटी नहीं है, इसलिए वे थ्रेड-सुरक्षित हैं। यह प्रोग्रामर पर निर्भर है कि वह समसामयिक मुद्दों को ठीक करे, इसलिए यह उत्तर पूरी तरह से उपयुक्त है यदि संग्रह थ्रेड सुरक्षित हैं।
निकोलस

1
@ निक्सन यह एक बेहतर समाधान की उपस्थिति में उपयुक्त नहीं है, जो हमारे यहां है। इस तरह के कोड होने से बुरी मिसाल पैदा हो सकती है, जिससे दूसरे लोग इसका गलत तरीके से इस्तेमाल कर सकते हैं। यहां तक ​​कि अगर कोई समानांतर धाराओं का उपयोग नहीं किया जाता है, तो यह केवल एक कदम दूर है। अच्छा कोडिंग अभ्यास हमें धारा संचालन के दौरान राज्य बनाए रखने की आवश्यकता नहीं है। अगली बात जो हम करते हैं वह अपाचे स्पार्क जैसे ढांचे में कोडिंग है, और समान प्रथाओं से वास्तव में अप्रत्याशित परिणाम प्राप्त होंगे। यह एक रचनात्मक समाधान था, मैं वह देता हूं, एक जिसे मैंने शायद बहुत पहले नहीं लिखा था।
योयो

1
@JoD यह एक बेहतर समाधान नहीं है, यह तथ्यात्मक रूप से अधिक अक्षम है। सोच की लाइन अंततः इस निष्कर्ष के साथ समाप्त होती है कि सभी संग्रह अनपेक्षित परिणामों को रोकने के लिए डिफ़ॉल्ट रूप से थ्रेड सुरक्षित होना चाहिए, जो कि बस गलत है।
निकोलस

301

इसके लिए एक कलेक्टर का उपयोग किया जा सकता है।

  • दो श्रेणियों के लिए, Collectors.partitioningBy()कारखाने का उपयोग करें ।

यह एक बनाएगा Mapसे Booleanकरने के लिए List, और या एक में आइटम एक के आधार पर अन्य सूची में डाल दिया Predicate

नोट: चूँकि धारा को पूरी खपत करने की आवश्यकता होती है, इसलिए यह अनंत धाराओं पर काम नहीं कर सकता है। और क्योंकि धारा का वैसे भी उपभोग किया जाता है, यह विधि बस उन्हें एक नई धारा के साथ स्मृति बनाने के बजाय सूचियों में डालती है। आप उन सूचियों को हमेशा स्ट्रीम कर सकते हैं यदि आपको आउटपुट के रूप में स्ट्रीम की आवश्यकता है।

इसके अलावा, पुनरावृत्ति की कोई ज़रूरत नहीं है, यहां तक ​​कि केवल आपके द्वारा प्रदान किए गए प्रमुख उदाहरण में भी नहीं।

  • बाइनरी विभाजन इस तरह दिखता है:
Random r = new Random();

Map<Boolean, List<String>> groups = stream
    .collect(Collectors.partitioningBy(x -> r.nextBoolean()));

System.out.println(groups.get(false).size());
System.out.println(groups.get(true).size());
  • अधिक श्रेणियों के लिए, एक Collectors.groupingBy()कारखाने का उपयोग करें ।
Map<Object, List<String>> groups = stream
    .collect(Collectors.groupingBy(x -> r.nextInt(3)));
System.out.println(groups.get(0).size());
System.out.println(groups.get(1).size());
System.out.println(groups.get(2).size());

यदि धाराएँ नहीं हैं Stream, लेकिन आदिम धाराओं में से एक जैसी हैं IntStream, तो यह .collect(Collectors)विधि उपलब्ध नहीं है। आपको इसे बिना कलेक्टर कारखाने के मैनुअल तरीके से करना होगा। यह कार्यान्वयन इस तरह दिखता है:

[उदाहरण २०२०-०४-१६ से २.०]

    IntStream    intStream = IntStream.iterate(0, i -> i + 1).limit(100000).parallel();
    IntPredicate predicate = ignored -> r.nextBoolean();

    Map<Boolean, List<Integer>> groups = intStream.collect(
            () -> Map.of(false, new ArrayList<>(100000),
                         true , new ArrayList<>(100000)),
            (map, value) -> map.get(predicate.test(value)).add(value),
            (map1, map2) -> {
                map1.get(false).addAll(map2.get(false));
                map1.get(true ).addAll(map2.get(true ));
            });

इस उदाहरण में मैं प्रारंभिक संग्रह के पूर्ण आकार के साथ एरेलेलिस्ट्स को इनिशियलाइज़ करता हूं (यदि यह सभी ज्ञात है)। यह सबसे खराब स्थिति में भी आकार बदलने की घटनाओं को रोकता है, लेकिन संभावित रूप से 2 * N * T स्पेस (N = तत्वों की प्रारंभिक संख्या, T = थ्रेड की संख्या) को टटोल सकता है। गति के लिए स्पेस-ऑफ करने के लिए, आप इसे छोड़ सकते हैं या अपने सबसे अच्छे शिक्षित अनुमान का उपयोग कर सकते हैं, जैसे कि एक विभाजन में तत्वों की अपेक्षित उच्चतम संख्या (आमतौर पर संतुलित विभाजन के लिए एन / 2 से अधिक)।

मुझे आशा है कि मैं जावा 9 विधि का उपयोग करके किसी को नाराज नहीं करता। जावा 8 संस्करण के लिए, संपादन इतिहास को देखें।


2
सुंदर। हालांकि, एक समानांतर धारा के मामले में इंटस्ट्रीम वॉन्ट के लिए अंतिम समाधान थ्रेड-सुरक्षित होगा। समाधान जितना आपको लगता है, उससे कहीं ज्यादा सरल है ... stream.boxed().collect(...);! यह विज्ञापित के रूप में करेगा: IntStreamबॉक्सिंग Stream<Integer>संस्करण के लिए आदिम परिवर्तित करें ।
योयो

32
यह स्वीकृत उत्तर होना चाहिए क्योंकि यह सीधे ओपी प्रश्न को हल करता है।
ejel

27
मैं चाहता हूं कि स्टैक ओवरफ्लो अगर बेहतर पाया जाता है, तो समुदाय चयनित उत्तर को ओवरराइड करने की अनुमति देगा।
गुइसीम

मुझे यकीन नहीं है कि यह सवाल का जवाब देता है। प्रश्न स्ट्रीम में स्ट्रीम को विभाजित करने का अनुरोध करता है - सूचियां नहीं।
एलिकएल्ज़िन-किलाका

1
संचायक फ़ंक्शन अनावश्यक रूप से क्रिया है। (map, x) -> { boolean partition = p.test(x); List<Integer> list = map.get(partition); list.add(x); }आप के बजाय बस का उपयोग कर सकते हैं (map, x) -> map.get(p.test(x)).add(x)। इसके अलावा, मुझे कोई कारण नहीं दिखता कि collectऑपरेशन थ्रेड-सुरक्षित क्यों न हो। यह ठीक वैसे ही काम करता है जैसे कि यह काम करने वाला होता है और कैसे Collectors.partitioningBy(p)काम करता है। लेकिन मैं जब दो बार मुक्केबाजी से बचने के लिए उपयोग नहीं कर रहा था , के IntPredicateबजाय का Predicate<Integer>उपयोग करेंगे boxed()
होल्गर

21

मैं अपने आप को इस सवाल पर ठोकर खाई और मुझे लगता है कि एक forked स्ट्रीम में कुछ उपयोग के मामले हैं जो वैध साबित हो सकते हैं। मैंने नीचे एक उपभोक्ता के रूप में कोड लिखा था ताकि यह कुछ भी न करे लेकिन आप इसे कार्यों पर लागू कर सकते हैं और कुछ और जो आप भर सकते हैं।

class PredicateSplitterConsumer<T> implements Consumer<T>
{
  private Predicate<T> predicate;
  private Consumer<T>  positiveConsumer;
  private Consumer<T>  negativeConsumer;

  public PredicateSplitterConsumer(Predicate<T> predicate, Consumer<T> positive, Consumer<T> negative)
  {
    this.predicate = predicate;
    this.positiveConsumer = positive;
    this.negativeConsumer = negative;
  }

  @Override
  public void accept(T t)
  {
    if (predicate.test(t))
    {
      positiveConsumer.accept(t);
    }
    else
    {
      negativeConsumer.accept(t);
    }
  }
}

अब आपका कोड कार्यान्वयन कुछ इस तरह हो सकता है:

personsArray.forEach(
        new PredicateSplitterConsumer<>(
            person -> person.getDateOfBirth().isPresent(),
            person -> System.out.println(person.getName()),
            person -> System.out.println(person.getName() + " does not have Date of birth")));

20

दुर्भाग्य से, आप जो मांगते हैं वह सीधे स्ट्रीम के जावाडॉक में होता है :

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

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

हालाँकि, आप पुनर्विचार करना चाह सकते हैं यदि Streamआपके उपयोग के मामले के लिए उपयुक्त संरचना है।


6
जावदोक शब्दांकन कई धाराओं में विभाजन को बाहर नहीं करता है जब तक कि एक एकल स्ट्रीम आइटम केवल इनमें से किसी एक में जाता है
थोरबजर्न रेव एंडरसन

2
@ ThorbjørnRavnAndersen मुझे यकीन नहीं है कि एक स्ट्रीम आइटम को डुप्लिकेट करना एक कांटेक्ट स्ट्रीम के लिए प्रमुख बाधा है। मुख्य मुद्दा यह है कि फोर्किंग ऑपरेशन अनिवार्य रूप से एक टर्मिनल ऑपरेशन है, इसलिए जब आप कांटा लगाने का निर्णय लेते हैं तो आप मूल रूप से किसी प्रकार का एक संग्रह बना रहे हैं। उदाहरण के लिए, मैं एक विधि लिख सकता हूं, List<Stream> forkStream(Stream s)लेकिन मेरी परिणामी धाराएं कम से कम आंशिक रूप से संग्रह द्वारा समर्थित होंगी और अंतर्निहित स्ट्रीम द्वारा सीधे नहीं, जैसा कि यह कहना है कि filterजो टर्मिनल स्ट्रीम ऑपरेशन नहीं है।
ट्रेवर फ्रीमैन

7
यह उन कारणों में से एक है, जो मुझे लगता है कि जावा स्ट्रीम github.com/ReactiveX/RxJava/wiki की तुलना में थोड़ी सी आधी है, क्योंकि धारा का बिंदु तत्वों के संभावित अनंत सेट पर परिचालन को लागू करना है और वास्तविक संचालन को अक्सर विभाजन की आवश्यकता होती है , नकल और धाराओं का विलय।
उस्मान इस्माइल

8

यह स्ट्रीम के सामान्य तंत्र के खिलाफ है। कहते हैं कि आप स्ट्रीम S0 को Sa और Sb में विभाजित कर सकते हैं जैसे आप चाहते थे। किसी भी टर्मिनल ऑपरेशन को निष्पादित करते हुए, कहते हैं count(), Sa पर आवश्यक रूप से S0 में सभी तत्वों का "उपभोग" किया जाएगा। इसलिए Sb ने अपना डेटा स्रोत खो दिया।

पहले, स्ट्रीम में एक tee()विधि थी, मुझे लगता है, जो एक स्ट्रीम को दो को डुप्लिकेट करता है। इसे अब हटा दिया गया है।

स्ट्रीम में एक झांकना () विधि है, हालांकि, आप अपनी आवश्यकताओं को प्राप्त करने के लिए इसका उपयोग करने में सक्षम हो सकते हैं।


1
peekवास्तव में क्या हुआ करता था tee
लुई वासरमैन

5

बिल्कुल नहीं, लेकिन आप इसे लागू करने में सक्षम हो सकते हैं जो आपको चाहिए Collectors.groupingBy()। आप एक नया संग्रह बनाते हैं, और फिर उस नए संग्रह पर धाराएँ प्रवाहित कर सकते हैं।


2

यह कम से कम बुरा जवाब था जिसके साथ मैं आ सकता था।

import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;

public class Test {

    public static <T, L, R> Pair<L, R> splitStream(Stream<T> inputStream, Predicate<T> predicate,
            Function<Stream<T>, L> trueStreamProcessor, Function<Stream<T>, R> falseStreamProcessor) {

        Map<Boolean, List<T>> partitioned = inputStream.collect(Collectors.partitioningBy(predicate));
        L trueResult = trueStreamProcessor.apply(partitioned.get(Boolean.TRUE).stream());
        R falseResult = falseStreamProcessor.apply(partitioned.get(Boolean.FALSE).stream());

        return new ImmutablePair<L, R>(trueResult, falseResult);
    }

    public static void main(String[] args) {

        Stream<Integer> stream = Stream.iterate(0, n -> n + 1).limit(10);

        Pair<List<Integer>, String> results = splitStream(stream,
                n -> n > 5,
                s -> s.filter(n -> n % 2 == 0).collect(Collectors.toList()),
                s -> s.map(n -> n.toString()).collect(Collectors.joining("|")));

        System.out.println(results);
    }

}

यह पूर्णांकों की एक धारा लेता है और उन्हें 5 पर विभाजित करता है। 5 से अधिक लोगों के लिए यह केवल संख्याओं को फ़िल्टर करता है और उन्हें एक सूची में रखता है। बाकी के लिए यह उनके साथ जुड़ जाता है |

आउटपुट:

 ([6, 8],0|1|2|3|4|5)

इसका आदर्श नहीं है क्योंकि यह धारा को तोड़ते हुए मध्यस्थ संग्रह में सब कुछ इकट्ठा करता है (और बहुत सारे तर्क हैं!)


1

मैं कुछ तत्वों को एक स्ट्रीम से फ़िल्टर करने और उन्हें त्रुटियों के रूप में लॉग इन करने के तरीके की तलाश करते हुए इस प्रश्न के पार पहुंच गया। तो मुझे वास्तव में स्ट्रीम को विभाजित करने की आवश्यकता नहीं थी, क्योंकि विनीत वाक्यविन्यास के साथ विधेय के लिए एक समयपूर्व समाप्ति कार्रवाई संलग्न करें। मैंने ये ढूंढ निकाला:

public class MyProcess {
    /* Return a Predicate that performs a bail-out action on non-matching items. */
    private static <T> Predicate<T> withAltAction(Predicate<T> pred, Consumer<T> altAction) {
    return x -> {
        if (pred.test(x)) {
            return true;
        }
        altAction.accept(x);
        return false;
    };

    /* Example usage in non-trivial pipeline */
    public void processItems(Stream<Item> stream) {
        stream.filter(Objects::nonNull)
              .peek(this::logItem)
              .map(Item::getSubItems)
              .filter(withAltAction(SubItem::isValid,
                                    i -> logError(i, "Invalid")))
              .peek(this::logSubItem)
              .filter(withAltAction(i -> i.size() > 10,
                                    i -> logError(i, "Too large")))
              .map(SubItem::toDisplayItem)
              .forEach(this::display);
    }
}

0

छोटा संस्करण जो लोम्बोक का उपयोग करता है

import java.util.function.Consumer;
import java.util.function.Predicate;

import lombok.RequiredArgsConstructor;

/**
 * Forks a Stream using a Predicate into postive and negative outcomes.
 */
@RequiredArgsConstructor
@FieldDefaults(makeFinal = true, level = AccessLevel.PROTECTED)
public class StreamForkerUtil<T> implements Consumer<T> {
    Predicate<T> predicate;
    Consumer<T> positiveConsumer;
    Consumer<T> negativeConsumer;

    @Override
    public void accept(T t) {
        (predicate.test(t) ? positiveConsumer : negativeConsumer).accept(t);
    }
}

-3

कैसा रहेगा:

Supplier<Stream<Integer>> randomIntsStreamSupplier =
    () -> (new Random()).ints(0, 2).boxed();

Stream<Integer> tails =
    randomIntsStreamSupplier.get().filter(x->x.equals(0));
Stream<Integer> heads =
    randomIntsStreamSupplier.get().filter(x->x.equals(1));

1
चूंकि आपूर्तिकर्ता को दो बार कहा जाता है, इसलिए आपको दो अलग-अलग यादृच्छिक संग्रह मिलेंगे। मुझे लगता है कि यह एक ही उत्पन्न अनुक्रम में
बुराइयों
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.