#java #java_stream
Есть Stream, как разбить его на несколько Stream или List, относительно тех строк, которые начинаются с определенного слова (аналогично split("word") на обычной строке)? Например, для "word": [word, hello, dude, word and word2, stackoverflow, question, ask, word, example] => [[hello, dude], [stackoverflow, question, ask], [example]] (Stream => Stream >)
Ответы
Ответ 1
Можно собрать в Map по группам (группа -> список слов), а затем развернуть значения. Пример: Streamsrc = Stream.of("word", "hello", "dude", "word and word2", "stackoverflow", "question", "ask", "word", "example"); final AtomicInteger group = new AtomicInteger(0); Stream > 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
> 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-ы - проще просто проитерироваться по коллекции и собрать результат в нужном виде.
Ответ 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 Listbuffered = new ArrayList<>(); private final List > data = new ArrayList<>(); private final Function
split_detector; private final boolean exclude_splitter; public Accum(Function 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 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 > getResults() { if ( buffered.size() > 0 ) data.add(0, buffered); return data; } } public static Collector
>> of(String splitter, boolean excludeSplitter, boolean ignoreCase) { Function 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 >, но переделать под Stream
> - дело несложное. Пример использования: 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 можно обобщить на любой тип данных. Обобщённую реализацию и примеры использования можно глянуть здесь.
Комментариев нет:
Отправить комментарий