Перепись вложенного цикла Stream API

Имеется код, который нужно переписать с использованием Stream API. Подскажите, как это можно сделать?

Моя попытка это реализовать:

text.getComponents()
    .stream()
    .flatMap(p -> p.getComponents()
        .stream()
        .flatMap(s -> s.getComponents()
            .stream()
            .filter(l -> l.getType() != TextComponentType.PUNCTUATION)
            .anyMatch(c -> c.getComponents().size() < min)
            .forEach((p.getComponents().remove(s));

Код самого цикла:

for (TextComponent paragraph : text.getComponents()) {
    for (TextComponent sentence : paragraph.getComponents()) {
        int numberOfWord = 0;
        for (TextComponent lexeme : sentence.getComponents()) {
            if (lexeme.getType() != TextComponentType.PUNCTUATION) {
                numberOfWord++;
            }
        }
        if (numberOfWord < min) {
            paragraph.removeText(sentence);
        }
    }
}

Ответы (1 шт):

Автор решения: Alex Rudenko

Не уверен, что в данном случае применение Stream API оправдано, так как фактически должны применяться вложенные forEach операции. Впрочем, и удаление предложений из параграфа во время итерации по коллекции предложений в версии с циклами вполне способно вызвать ConcurrentModificationException.

Лобовое решение на стримах может выглядеть так:

text.getComponents().stream().forEach(
    parag -> parag.getComponents().stream() // поток предложений
        .filter(s -> s.getComponents().stream() // поток лексем
            .filter(lex -> lex.getType() != TextComponentType.PUNCTUATION)
            .count() < min
        ) // отфильтрованный поток предложений
        .forEach(parag::removeText)
);

Более корректным решением может быть сначала определить список предложений, подлежащих удалению, а потом уже удалять их

text.getComponents().stream().forEach(
    parag -> {
        List<TextComponent> shortSentences = parag.getComponents()
            .stream() // поток предложений
            .filter(s -> s.getComponents().stream() // поток лексем
                .filter(lex -> lex.getType() != TextComponentType.PUNCTUATION)
                .count() < min
            ) // отфильтрованный поток предложений
            .collect(Collectors.toList());

        shortSentences.forEach(parag::removeText);
// или parag.getComponents().removeAll(shortSentences) если такое допустимо
    }
);

Ещё один вариант -- удалять элементы из коллекции TextComponent, используя removeIf, если это допускается в данной реализации, т.е. коллекция модифицируема -- Stream API здесь применяется частично

text.getComponents().forEach(
    p -> p.getComponents().removeIf(
        s -> s.getComponents().stream()
            .filter(lex -> lex.getType() != TextComponentType.PUNCTUATION)
            .count() < min
    )
);
→ Ссылка