wake-up-neo.net

Auswahl von Elementen einer Liste, bis die Bedingung mit Java 8 Lambdas erfüllt ist

Ich versuche, meine Meinung zu ändern, um die Funktionsweise zu überdenken, und vor kurzem sah ich mich einer Situation gegenüber, in der ich Elemente von einer Liste aufheben musste, bis eine Bedingung erfüllt ist und ich keinen natürlichen Weg finden konnte, um dies zu erreichen. Natürlich lerne ich immer noch.

Sagen Sie, ich habe diese Liste:

List<String> tokens = Arrays.asList("pick me", "Pick me", "pick Me",
    "PICK ME", "pick me and STOP", "pick me", "pick me and Stop", "pick me");

// In a non lambdas was you would do it like below
List<String> myTokens = new ArrayList<>();
for (String token : tokens) {
    myTokens.add(token);
    if (token.toUpperCase().endsWith("STOP")) {
        break;
    }
}

Vielen Dank im Voraus für Ihre Beiträge

HINWEIS: Bevor ich das veröffentlichte, lese beschränke ich einen Stream durch ein Prädikat aber ich konnte nicht sehen, wie ich diese Antwort an mein Problem anpassen kann. Jede Hilfe wäre dankbar dankbar.

15
Julian

Eine Option verwendet einen Collector, der zwei Funktionen benötigt, eine, die Strings zu Listen hinzufügt, und eine andere, die zuvor parallel erstellte Listen kombiniert. Sie fügt den String oder die gesamte Liste nur dann hinzu, wenn die vorherige Teilausgabe nicht mit einem Element endet, das mit STOP endet:

tokens.stream().collect(() -> new ArrayList<String>(), (l, e) -> {
    if(l.isEmpty() || !l.get(l.size()-1).toUpperCase().endsWith("STOP"))
        l.add(e);
}, (l1, l2) -> {
    if(l1.isEmpty() || !l1.get(l1.size()-1).toUpperCase().endsWith("STOP"))
        l1.addAll(l2);
});
2
WillShackleford

In JDK9 wird es eine neue Stream-Operation mit dem Namen takeWhile geben, die genau das tut, was Sie brauchen. Ich habe diese Operation in meine StreamEx - Bibliothek zurückportiert, sodass Sie sie auch in Java-8 verwenden können:

List<String> list = StreamEx.of(tokens)
                            .takeWhile(t -> !t.toUpperCase().endsWith("STOP"))
                            .toList();

Leider nimmt es nicht das "STOP"-Element selbst, daher muss der zweite Durchlauf manuell hinzugefügt werden:

list.add(StreamEx.of(tokens).findFirst(t -> t.toUpperCase().endsWith("STOP")).get());

Beachten Sie, dass sowohl takeWhile als auch findFirst Kurzschlussoperationen sind (sie werden nicht den gesamten Eingabestream verarbeiten, wenn dies nicht unnötig ist), sodass Sie sie mit sehr langen oder sogar unendlichen Streams verwenden können.

Wenn Sie jedoch StreamEx verwenden, können Sie es in einem Durchgang lösen, indem Sie den Trick mit groupRuns verwenden. Die Methode groupRuns gruppiert benachbarte Stream-Elemente zu List basierend auf dem angegebenen Prädikat, das angibt, ob zwei gegebene benachbarte Elemente gruppiert werden sollen oder nicht. Wir können berücksichtigen, dass die Gruppe mit dem Element endet, das "STOP" enthält. Dann müssen wir nur die erste Gruppe nehmen:

List<String> list = StreamEx.of(tokens)
                            .groupRuns((a, b) -> !a.toUpperCase().endsWith("STOP"))
                            .findFirst().get();

Diese Lösung leistet auch keine zusätzliche Arbeit, wenn die erste Gruppe abgeschlossen ist.

13
Tagir Valeev

Wenn Sie wirklich Streams-API verwenden müssen, halten Sie es einfach und verwenden Sie einen Stream von Indizes:

int lastIdx = IntStream.range(0, tokens.size())
        .filter(i -> tokens.get(i).toUpperCase().endsWith("STOP"))
        .findFirst()
        .orElse(-1);

List<String> myTokens = tokens.subList(0, lastIdx + 1);

Oder machen Sie aus der Unterliste einen neuen Listname__, wenn Sie eine unabhängige Kopie wünschen, die nicht von der ursprünglichen Liste unterstützt wird.

9
Misha

Verwenden Sie ausschließlich Java 8 API:

public static <R> Stream<? extends R> takeUntil(Iterator<R> iterator, Predicate<? super R> stopFilter) {

    return StreamSupport.stream(Spliterators.spliteratorUnknownSize(new Iterator<R>() {
        private R next = null;
        private boolean hasTaken = true;
        private boolean stopIteration = !iterator.hasNext();
        @Override
        public boolean hasNext() {
            if (stopIteration) {
                return false;
            }

            if (!hasTaken) {
                return true;
            }

            if (!iterator.hasNext()) {
                stopIteration = true;
                return false;
            }

            next = iterator.next();
            stopIteration = stopFilter.test(next);
            hasTaken = stopIteration;
            return !stopIteration;
        }

        @Override
        public R next() {
            if (!hasNext()) {
                throw new NoSuchElementException("There are no more items to consume");
            }
            hasTaken = true;
            return next;
        }
    }, 0), false);
}

Sie können es dann folgendermaßen spezialisieren:

Für Streams

public static <R> Stream<? extends R> takeUntil(Stream<R> stream, Predicate<? super R> stopFilter) {
    return takeUntil(stream.iterator(), stopFilter);
}

Für Sammlungen

public static <R> Stream<? extends R> takeUntil(Collection<R> col, Predicate<? super R> stopFilter) {
    return takeUntil(col.iterator(), stopFilter);
}
1
smac89

Obwohl die obigen Antworten vollkommen gültig sind, müssen sie vor der Verarbeitung der Elemente collect und/oder pre-fetch die Elemente abrufen (beide Elemente können ein Problem darstellen, wenn der Stream sehr lang ist).

Für meine Bedürfnisse habe ich daher Louis 'Antwort auf die von Julian aufgeworfene Frage angepasst und sie so angepasst, dass der Stop/Break-Punkt erhalten bleibt. Siehe den Parameter keepBreak ::

public static <T> Spliterator<T> takeWhile(final Spliterator<T> splitr, final Predicate<? super T> predicate, final boolean keepBreak) {
    return new Spliterators.AbstractSpliterator<T>(splitr.estimateSize(), 0) {
        boolean stillGoing = true;

        @Override
        public boolean tryAdvance(final Consumer<? super T> consumer) {
            if (stillGoing) {
                final boolean hadNext = splitr.tryAdvance(elem -> {
                    if (predicate.test(elem)) {
                        consumer.accept(elem);
                    } else {
                        if (keepBreak) {
                            consumer.accept(elem);
                        }
                        stillGoing = false;
                    }
                });
                return hadNext && (stillGoing || keepBreak);
            }
            return false;
        }
    };
}

public static <T> Stream<T> takeWhile(final Stream<T> stream, final Predicate<? super T> predicate, final boolean keepBreak) {
    return StreamSupport.stream(takeWhile(stream.spliterator(), predicate, keepBreak), false);
}

Verwendung:

public List<String> values = Arrays.asList("some", "words", "before", "BREAK", "AFTER");

    @Test
    public void testStopAfter() {
        Stream<String> stream = values.stream();
        //how to filter stream to stop at the first BREAK
        stream = stream.filter(makeUntil(s -> "BREAK".equals(s)));
        final List<String> actual = stream.collect(Collectors.toList());

        final List<String> expected = Arrays.asList("some", "words", "before", "BREAK");
        assertEquals(expected, actual);
    }

Disclaimer : Ich bin nicht zu 100% sicher, dass dies auf parallelen (der neue Stream ist sicherlich nicht parallel) oder nicht sequentiellen Streams funktioniert. Bitte kommentieren/bearbeiten, wenn Sie dazu einige Hinweise haben.

1
Benoît