Разбиение Stream<String>

364
08 августа 2017, 14:36

Есть Stream<String>, как разбить его на несколько Stream или List, относительно тех строк, которые начинаются с определенного слова (аналогично split("word") на обычной строке)?

Например, для "word":

[word, hello, dude, word and word2, stackoverflow, question, ask, word, example]
=>
[[hello, dude], [stackoverflow, question, ask], [example]]
(Stream<String> => Stream<Stream<String>>)
Answer 1

Можно собрать в Map по группам (группа -> список слов), а затем развернуть значения. Пример:

    Stream<String> src = Stream.of("word", "hello", "dude", "word and word2", "stackoverflow", "question", "ask", "word", "example");
    final AtomicInteger group = new AtomicInteger(0);
    Stream<Stream<String>> streamResult = src.map(s -> new AbstractMap.SimpleEntry<>(s, s.startsWith("word") ? 0 * group.incrementAndGet() - 1 : group.get()))
            .filter(entry -> entry.getValue() > -1)
            .collect(Collectors.groupingBy(Map.Entry::getValue, Collectors.mapping(Map.Entry::getKey, Collectors.toList())))
            .values().stream().map(List::stream);

Если нужен результат в виде List<List<String>> то:

    List<List<String>> listResult = new ArrayList<>(src.map(s -> new AbstractMap.SimpleEntry<>(s, s.startsWith("word") ? 0 * group.incrementAndGet() - 1 : group.get()))
            .filter(entry -> entry.getValue() > -1)
            .collect(Collectors.groupingBy(Map.Entry::getValue, Collectors.mapping(Map.Entry::getKey, Collectors.toList())))
            .values());  

Хотя в вашей ситуации непонятно зачем использовать именно stream-ы - проще просто проитерироваться по коллекции и собрать результат в нужном виде.

Answer 2

Раз уж отметился в комментах, то приведу свою реализацию через Collector.
Кстати, данная реализация будет корректно работать и в случае распараллеливания Stream.

import java.util.*;
import java.util.function.*;
import java.util.stream.*;
public final class StringSequenceSplitter
{
    private StringSequenceSplitter() { }
    private static class Accum
    {
        private final List<String> buffered = new ArrayList<>();
        private final List<List<String>> data = new ArrayList<>();
        private final Function<String,Boolean> split_detector;
        private final boolean exclude_splitter;
        public Accum(Function<String,Boolean> split_detector, boolean exclude_splitter)
        {
            this.split_detector = split_detector;
            this.exclude_splitter = exclude_splitter;
        }
        public Accum pushElement(String element)
        {
            boolean new_seq = split_detector.apply(element);
            if ( new_seq )
            {
                List<String> new_list = new ArrayList<>();
                if ( ! exclude_splitter )
                    new_list.add(element);
                data.add(new_list);
            }
            else if ( data.size() > 0 )
                data.get(data.size() - 1).add(element);
            else
                buffered.add(element);
            return this;
        }
        public static Accum combine(Accum a1, Accum a2)
        {
            if ( a1.data.size() > 0 )
            {
                a1.data.get(a1.data.size() - 1).addAll(a2.buffered);
                a1.data.addAll(a2.data);
            }
            else
            {
                a1.buffered.addAll(a2.buffered);
                a1.data.addAll(a2.data);
            }
            return a1;
        }
        public List<List<String>> getResults()
        {
            if ( buffered.size() > 0 )
                data.add(0, buffered);
            return data;
        }
    }
    public static Collector<String, ?, List<List<String>>> of(String splitter, boolean excludeSplitter, boolean ignoreCase)
    {
        Function<String, Boolean> split_detector =
            ignoreCase ?
            s -> (s.length() >= splitter.length()) && s.substring(0, splitter.length()).equalsIgnoreCase(splitter) :
            s -> s.startsWith(splitter);
        return Collector.of
        (
            () -> new Accum(split_detector, excludeSplitter),
            Accum::pushElement,
            Accum::combine,
            Accum::getResults
        );
    }
}

Данная реализация Collector возвращает List<List<String>>, но переделать под Stream<Stream<String>> - дело несложное.
Пример использования:

Stream.of("word", "hello", "dude", "word and word2", "stackoverflow", "question", "ask", "word", "example")
    .parallel()  // для демонстрации работы с параллельными Stream
    .collect(StringSequenceSplitter.of("word", true, false))
    .forEach(list -> System.out.println("  " + list.toString()));

Напоследок, хотелось бы предостеречь автора от неосторожного использования вложенных объектов Stream.
Stream - объект одноразовый, в том смысле что получить данные из него можно только однажды. Если потребуется повторно запросить данные, то нужно создавать новый объект Stream (а для этого надо иметь в наличии исходный массив или коллекцию, от которой получали Stream).
Для иллюстрации "одноразовости" объектов Stream приведу следующий код.
(даёт ошибку в 3-й строке)

Stream.of(Stream.of("1", "2"), Stream.of("1", "2", "3"))
    .filter(stream -> stream.count() > 2)
    .forEach(stream -> System.out.println(stream.findFirst().orElse("-")));

P.S.:
Приведённую реализацию Collector можно обобщить на любой тип данных.
Обобщённую реализацию и примеры использования можно глянуть здесь.

READ ALSO
Что означает включение и выключение autocommit в Hibernate?

Что означает включение и выключение autocommit в Hibernate?

В чем разница в поведении в случаях если я устанавливаю hibernateconnection

291
Как сделать, чтобы после выбора пункта из Spinner появлялся новый Spinner (которого изначально не было)

Как сделать, чтобы после выбора пункта из Spinner появлялся новый Spinner (которого изначально не было)

Например: в первом списке мы выбираем страну, появляется новый (ниже первого), там выбираем город, появляется новый и тд пока не дойдем до конечного...

335
Кнопка в определенных координатах

Кнопка в определенных координатах

Суть: нужна кнопка которая появляется при определённом событии в 2D игре(не меню)Не всплывающее окно и не JButton

283
Register CLOB fail or no

Register CLOB fail or no

Есть такой вопросУ меня в базе есть пакет с функцией, который возвращает CLOB

325