Вопрос-ответ

Collect successive pairs from a stream

Сбор последовательных пар из потока

Учитывая объект или примитивный поток, такой как { 0, 1, 2, 3, 4 }, как я могу наиболее элегантно преобразовать его в заданную форму (предполагая, конечно, что я определил класс Pair)?

{ new Pair(0, 1), new Pair(1, 2), new Pair(2, 3), new Pair(3, 4) }

Переведено автоматически
Ответ 1

Библиотека потоков Java 8 в первую очередь ориентирована на разделение потоков на более мелкие фрагменты для параллельной обработки, поэтому этапы конвейера с отслеживанием состояния довольно ограничены, и выполнение таких действий, как получение индекса текущего элемента потока и доступ к соседним элементам потока, не поддерживается.

Типичный способ решения этих проблем, с некоторыми ограничениями, конечно, состоит в том, чтобы управлять потоком по индексам и полагаться на обработку значений в некоторой структуре данных с произвольным доступом, такой как ArrayList, из которой могут быть извлечены элементы. Если бы значения были в arrayList, можно было бы сгенерировать пары в соответствии с запросом, выполнив что-то вроде этого:

    IntStream.range(1, arrayList.size())
.mapToObj(i -> new Pair(arrayList.get(i-1), arrayList.get(i)))
.forEach(System.out::println);

Конечно, ограничение заключается в том, что входные данные не могут быть бесконечным потоком. Однако этот конвейер можно запускать параллельно.

Ответ 2

Моя библиотека StreamEx, расширяющая стандартные потоки, предоставляет pairMap метод для всех типов потоков. Для примитивных потоков он не изменяет тип потока, но может использоваться для выполнения некоторых вычислений. Наиболее распространенное использование - вычисление различий:

int[] pairwiseDiffs = IntStreamEx.of(input).pairMap((a, b) -> (b-a)).toArray();

Для объектного потока вы можете создать любой другой тип объекта. Моя библиотека не предоставляет никаких новых видимых пользователем структур данных, таких как Pair (это часть концепции библиотеки). Однако, если у вас есть свой собственный Pair класс и вы хотите его использовать, вы можете сделать следующее:

Stream<Pair> pairs = IntStreamEx.of(input).boxed().pairMap(Pair::new);

Или, если у вас уже есть несколькоStream:

Stream<Pair> pairs = StreamEx.of(stream).pairMap(Pair::new);

Эта функциональность реализована с помощью пользовательского разделителя. Он имеет довольно низкие накладные расходы и может хорошо распараллеливаться. Конечно, это работает с любым источником потока, а не только со списком произвольного доступа / массивом, как многие другие решения. Во многих тестах это работает действительно хорошо. Вот тест JMH, в котором мы находим все входные значения, предшествующие большему значению, используя разные подходы (см. Этот вопрос).

Ответ 3

Вы можете сделать это с помощью метода Stream.reduce() (я не видел других ответов, использующих этот метод).

public static <T> List<Pair<T, T>> consecutive(List<T> list) {
List<Pair<T, T>> pairs = new LinkedList<>();
list.stream().reduce((a, b) -> {
pairs.add(new Pair<>(a, b));
return b;
});
return pairs;
}
Ответ 4

Это не элегантно, это хакерское решение, но работает для бесконечных потоков

Stream<Pair> pairStream = Stream.iterate(0, (i) -> i + 1).map( // natural numbers
new Function<Integer, Pair>() {
Integer previous;

@Override
public Pair apply(Integer integer) {
Pair pair = null;
if (previous != null) pair = new Pair(previous, integer);
previous = integer;
return pair;
}
}).skip(1); // drop first null

Теперь вы можете ограничить свой поток нужной длиной

pairStream.limit(1_000_000).forEach(i -> System.out.println(i));

P.S. Я надеюсь, что есть лучшее решение, что-то вроде clojure (partition 2 1 stream)

java java-8 java-stream