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

103

Учитывая такой поток, как { 0, 1, 2, 3, 4 },

как мне наиболее элегантно преобразовать его в заданную форму:

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

(при условии, конечно, что я определил класс Pair)?

Изменить: это не совсем касается целых или примитивных потоков. Ответ должен быть общим для потока любого типа.

Александр Дубинский
источник
2
Термин из FP - это «раздел», но я не нашел в Java метода с желаемой семантикой. Он имеет разбиение по предикату.
Марко Топольник
1
Обычно разделитель в JDK 8 предназначен для целей обхода и разделения. Я также попытаюсь привести пример.
Olimpiu POP
list.stream().map(i -> new Pair(i, i+1));
aepurniet
2
Для эквивалентной не потоков вопрос, см stackoverflow.com/questions/17453022/...
Raedwald
Кстати, некоторые используют любую реализацию Map.Entryкак класс Pair. (Конечно, некоторые могут подумать, что это взлом, но использование встроенного класса удобно.)
Бэзил Бурк,

Ответы:

33

Моя библиотека 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, где мы находим все входные значения, предшествующие большему значению, используя разные подходы (см. Этот вопрос).

Тагир Валеев
источник
Спасибо! Чем больше я изучаю эту библиотеку, тем больше она мне нравится. Я могу наконец начать использовать потоки. ( StreamExреализует Iterable! Ура!)
Александр Дубинский
Чтобы ваш ответ был на 100% полным, не могли бы вы показать, как Streamпревратить a в StreamEx?
Александр Дубинский
3
@AleksandrDubinsky: просто используйте StreamEx.of(stream). Есть и другие удобные статические методы создания потока из Collection, массива Readerи т.д. Отредактировал ответ.
Тагир Валеев
@TagirValeev pairMapзаказывается на последовательных потоках? На самом деле, я бы хотел иметь forPairsOrdered (), но, поскольку такого метода нет, могу ли я как-то его смоделировать? stream.ordered().forPairs()или stream().pairMap().forEachOrdered()?
Аскар Калыков
1
@AskarKalykov, pairMapэто промежуточная операция с невмешивающейся функцией сопоставления без сохранения состояния, для нее не указывается порядок так же, как для простой map. forPairsЯвляется неупорядоченной спецификацией, но неупорядоченные операции де-факто заказана для последовательных потоков. Было бы неплохо, если бы вы сформулировали свою исходную проблему как отдельный вопрос о стеке, чтобы предоставить больше контекста.
Тагир Валеев
74

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

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

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

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

Стюарт Маркс
источник
5
«Вход не может быть бесконечным потоком». На самом деле, вход вообще не может быть потоком. На arrayListсамом деле input ( ) - это коллекция, поэтому я не пометил ее как ответ. (Но поздравляю с золотым значком!)
Александр Дубинский
16

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

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));

PS Надеюсь, есть решение получше, что-то вроде clojure(partition 2 1 stream)

Mishadoff
источник
6
Престижность за указание на то, что анонимные классы иногда являются полезной альтернативой лямбдам.
Александр Дубинский
2
@aepurniet Я полагаю, это не будет работать правильно. Согласно parallelStreamдокументу: «Чтобы сохранить правильное поведение, эти поведенческие параметры не должны мешать, и в большинстве случаев должны быть без гражданства»
mishadoff
14
Это полностью противоречит конструкции структуры потоков и напрямую нарушает контракт API карты, поскольку анонимная функция не имеет состояния. Попробуйте запустить это с параллельным потоком и большим количеством данных, чтобы структура потока создавала больше рабочих потоков, и вы увидите результат: редкие случайные «ошибки» практически невозможно воспроизвести и их трудно обнаружить, пока у вас не будет достаточно данных (в производстве?). Это может иметь катастрофические последствия.
Марио Росси,
4
@AleksandrDubinsky Вы ошибаетесь относительно возможности распараллеливания limit / skip; реализация, представленная в JDK, фактически работает параллельно. Поскольку операция привязана к порядку обнаружения, распараллеливание не всегда может обеспечить выигрыш в производительности, но в ситуациях с высоким Q - может.
Брайан Гетц
4
@AleksandrDubinsky Неправильно. Он может пропустить случайный элемент , если поток неупорядоченный (имеет не определен порядок встречи, поэтому логически там нет никакого «первого» или «энный» элемент, только элементы.) Но является ли поток упорядоченный или неупорядоченный, пропускать всегда был в состоянии работать параллельно. Если поток упорядочен, параллелизма для извлечения меньше, но он все еще параллелен.
Брайан Гетц
15

Я реализовал оболочку сплитератора, которая берет все nэлементы Tиз исходного сплитератора и производит List<T>:

public class ConsecutiveSpliterator<T> implements Spliterator<List<T>> {

    private final Spliterator<T> wrappedSpliterator;

    private final int n;

    private final Deque<T> deque;

    private final Consumer<T> dequeConsumer;

    public ConsecutiveSpliterator(Spliterator<T> wrappedSpliterator, int n) {
        this.wrappedSpliterator = wrappedSpliterator;
        this.n = n;
        this.deque = new ArrayDeque<>();
        this.dequeConsumer = deque::addLast;
    }

    @Override
    public boolean tryAdvance(Consumer<? super List<T>> action) {
        deque.pollFirst();
        fillDeque();
        if (deque.size() == n) {
            List<T> list = new ArrayList<>(deque);
            action.accept(list);
            return true;
        } else {
            return false;
        }
    }

    private void fillDeque() {
        while (deque.size() < n && wrappedSpliterator.tryAdvance(dequeConsumer))
            ;
    }

    @Override
    public Spliterator<List<T>> trySplit() {
        return null;
    }

    @Override
    public long estimateSize() {
        return wrappedSpliterator.estimateSize();
    }

    @Override
    public int characteristics() {
        return wrappedSpliterator.characteristics();
    }
}

Для создания последовательного потока можно использовать следующий метод:

public <E> Stream<List<E>> consecutiveStream(Stream<E> stream, int n) {
    Spliterator<E> spliterator = stream.spliterator();
    Spliterator<List<E>> wrapper = new ConsecutiveSpliterator<>(spliterator, n);
    return StreamSupport.stream(wrapper, false);
}

Пример использования:

consecutiveStream(Stream.of(0, 1, 2, 3, 4, 5), 2)
    .map(list -> new Pair(list.get(0), list.get(1)))
    .forEach(System.out::println);
Томек Ренкавек
источник
Это повторяет каждый элемент дважды?
Александр Дубинский
Нет. Он создает новый поток, содержащий List<E>элементы. Каждый список содержит nпоследовательные элементы из исходного потока. Проверьте это сами;)
Томек Ренкавек
Не могли бы вы изменить свой ответ так, чтобы каждый элемент (кроме первого и последнего) повторялся?
Александр Дубинский
4
+1 Я считаю, что это хорошая работа, и ее следует обобщить на любой размер шага помимо размера раздела. (partition size step)Функция очень нужна, и это лучший способ ее получить.
Марко Топольник
3
Рассмотрите возможность использования ArrayDequeдля повышения производительности, а не LinkedList.
Марко Топольник
14

Вы можете сделать это с помощью метода 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;
}
СамТеббс33
источник
1
Он вернет (1,2) (2,3) вместо (1,2) (3,4). Также я не уверен, будет ли это применяться по порядку (конечно, нет гарантии этого).
Александр Дубинский
1
Пожалуйста, проверьте вопрос, это предполагаемое поведение @Aleksandr Dubinsky
SamTebbs33
4
Ах да, прости. И подумать, я это написал.
Александр Дубинский
6

Вы можете сделать это в cyclops-react (я участвую в этой библиотеке), используя оператор скольжения.

  LazyFutureStream.of( 0, 1, 2, 3, 4 )
                  .sliding(2)
                  .map(Pair::new);

Или

   ReactiveSeq.of( 0, 1, 2, 3, 4 )
                  .sliding(2)
                  .map(Pair::new);

Предполагая, что конструктор Pair может принимать коллекцию с двумя элементами.

Если вы хотите сгруппировать по 4 и увеличить на 2, это также поддерживается.

     ReactiveSeq.rangeLong( 0L,Long.MAX_VALUE)
                .sliding(4,2)
                .forEach(System.out::println);

Эквивалентные статические методы для создания скользящего представления для java.util.stream.Stream также предоставляются в классе циклоп-потоков StreamUtils .

       StreamUtils.sliding(Stream.of(1,2,3,4),2)
                  .map(Pair::new);

Примечание: - для однопоточной работы больше подходит ReactiveSeq. LazyFutureStream расширяет ReactiveSeq, но в первую очередь предназначен для одновременного / параллельного использования (это Stream of Futures).

LazyFutureStream расширяет ReactiveSeq, который расширяет Seq из удивительного jOOλ (который расширяет java.util.stream.Stream), поэтому решения, представленные Лукасом, также будут работать с любым типом Stream. Для всех, кто интересуется, основные различия между операторами окна / скольжения заключаются в очевидном соотношении относительной мощности / сложности и пригодности для использования с бесконечными потоками (скольжение не потребляет поток, а буферизует его по мере его потока).

Джон МакКлин
источник
Таким образом вы получите [(0,1) (2,3) ...], но вопрос задает [(0,1) (1,2) ...]. Пожалуйста, посмотрите мой ответ с помощью RxJava ...
frhack
1
Вы правы, плохо, я неправильно понял вопрос - здесь нужно использовать скользящий оператор. Я обновлю свой ответ - спасибо!
Джон МакКлин
4

Библиотека proton-pack обеспечивает оконную функциональность. Учитывая класс Pair и Stream, вы можете сделать это следующим образом:

Stream<Integer> st = Stream.iterate(0 , x -> x + 1);
Stream<Pair<Integer, Integer>> pairs = StreamUtils.windowed(st, 2, 1)
                                                  .map(l -> new Pair<>(l.get(0), l.get(1)))
                                                  .moreStreamOps(...);

Теперь pairsпоток содержит:

(0, 1)
(1, 2)
(2, 3)
(3, 4)
(4, ...) and so on
Алексис С.
источник
Однако похоже, что вам нужно создать stдважды! Может ли эта библиотека решить проблему с помощью одного потока?
Александр Дубинский
@AleksandrDubinsky Я не думаю, что это доступно с текущими сплитераторами. Я отправил проблему github.com/poetix/protonpack/issues/9
Alexis C.
@AleksandrDubinsky windowedФункционал добавлен! Смотрите редактирование.
Alexis C.
1
Почему бы вам не удалить свой старый ответ, чтобы другие пользователи могли видеть решение, а не историю.
Александр Дубинский,
4

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

Если вы хотите использовать стороннюю библиотеку и не нуждаетесь в параллелизме, тогда jOOλ предлагает оконные функции в стиле SQL следующим образом

System.out.println(
Seq.of(0, 1, 2, 3, 4)
   .window()
   .filter(w -> w.lead().isPresent())
   .map(w -> tuple(w.value(), w.lead().get())) // alternatively, use your new Pair() class
   .toList()
);

Уступая

[(0, 1), (1, 2), (2, 3), (3, 4)]

lead()Функция получает доступ следующего значения в порядке обхода из окна.

Нахождение последовательных троек / четверок / кортежей

Вопрос в комментариях просил о более общем решении, в котором должны собираться не пары, а n-кортежи (или, возможно, списки). Вот альтернативный подход:

int n = 3;

System.out.println(
Seq.of(0, 1, 2, 3, 4)
   .window(0, n - 1)
   .filter(w -> w.count() == n)
   .map(w -> w.window().toList())
   .toList()
);

Получение списка списков

[[0, 1, 2], [1, 2, 3], [2, 3, 4]]

Без него filter(w -> w.count() == n)результат был бы

[[0, 1, 2], [1, 2, 3], [2, 3, 4], [3, 4], [4]]

Отказ от ответственности: я работаю в компании, стоящей за jOOλ

Лукас Эдер
источник
Интересный. Что, если мне нужно сгруппировать 3 или более элемента? Использовать w.lead().lead()?
Рауль Сантелис,
1
@RaulSantelices: tuple(w.value(), w.lead(1), w.lead(2))был бы вариант. Я обновил свой ответ более общим решением дляlength = n
Лукас Эдер
1
Я правильно понимаю, что .window()это не ленивая операция, которая собирает весь входной поток в какую-то промежуточную коллекцию, а затем создает из нее новый поток?
Тагир Валеев
@TagirValeev: Да, это текущая реализация. Не в вышеописанном случае (не Comparatorиспользуется для изменения порядка окон), то оптимизация , как это было бы возможно, и, вероятно, будет реализован в будущем.
Лукас Эдер
2

Мы можем использовать RxJava (очень мощная библиотека реактивных расширений )

IntStream intStream  = IntStream.iterate(1, n -> n + 1);

Observable<List<Integer>> pairObservable = Observable.from(intStream::iterator).buffer(2,1);

pairObservable.take(10).forEach(b -> {
            b.forEach(n -> System.out.println(n));
            System.out.println();
        });

Буфера оператор преобразует наблюдаемый , который излучает предметы в наблюдаемый , который излучает буферном коллекции этих элементов ..

ебать
источник
1
Я использовал Observable.zip(obs, obs.skip(1), pair->{...})до сих пор! Я не знал, Observable.bufferчто есть версия со ступенькой (и привык к zipхитрости от python). +1
Реут Шарабани
1

Операция, по сути, имеет состояние, поэтому не совсем то, что потоки предназначены для решения - см. Раздел «Поведение без отслеживания состояния» в javadoc :

Наилучший подход - избегать использования поведенческих параметров с отслеживанием состояния для полной потоковой передачи операций.

Одно из решений здесь - ввести состояние в ваш поток через внешний счетчик, хотя он будет работать только с последовательным потоком.

public static void main(String[] args) {
    Stream<String> strings = Stream.of("a", "b", "c", "c");
    AtomicReference<String> previous = new AtomicReference<>();
    List<Pair> collect = strings.map(n -> {
                            String p = previous.getAndSet(n);
                            return p == null ? null : new Pair(p, n);
                        })
                        .filter(p -> p != null)
                        .collect(toList());
    System.out.println(collect);
}


static class Pair<T> {
    private T left, right;
    Pair(T left, T right) { this.left = left; this.right = right; }
    @Override public String toString() { return "{" + left + "," + right + '}'; }
}
ассилий
источник
Вопрос предлагает собрать последовательные элементы входного потока, а не просто собрать последовательные целые числа. Важное уточнение терминологии Stream:! = "Лямбды".
Александр Дубинский
Вы можете заменить AtomicInteger на AtomicReference. Альтернатива - использовать собственный сборщик или использовать внешние библиотеки, например, в этом примере: stackoverflow.com/a/30090528/829571
assylias
Смотрите мою правку. Также я не уверен, что понимаю ваш комментарий к потоку lambda! =. Другой ответ, в котором используется анонимный класс, делает по сути то же самое, за исключением того, что состояние удерживается анонимным классом, а не внешним ...
assylias
1
Это работает. StreamExБиблиотека также хорошая находка , и может быть ответ сам по себе. Мой комментарий к «streams! = Lambdas» относится к тому, что вы заявляете: «Операция, по сути, с отслеживанием состояния, поэтому не совсем то, что лямбды предназначены для решения». Я думаю, вы хотели использовать слово «потоки».
Александр Дубинский
О, понятно - я это разъяснил.
assylias
0

В вашем случае я бы написал свою собственную функцию IntFunction, которая отслеживает последний переданный int и использует его для сопоставления исходного IntStream.

import java.util.function.IntFunction;
import java.util.stream.IntStream;

public class PairFunction implements IntFunction<PairFunction.Pair> {

  public static class Pair {

    private final int first;
    private final int second;

    public Pair(int first, int second) {
      this.first = first;
      this.second = second;
    }

    @Override
    public String toString() {
      return "[" + first + "|" + second + "]";
    }
  }

  private int last;
  private boolean first = true;

  @Override
  public Pair apply(int value) {
    Pair pair = !first ? new Pair(last, value) : null;
    last = value;
    first = false;
    return pair;
  }

  public static void main(String[] args) {

    IntStream intStream = IntStream.of(0, 1, 2, 3, 4);
    final PairFunction pairFunction = new PairFunction();
    intStream.mapToObj(pairFunction)
        .filter(p -> p != null) // filter out the null
        .forEach(System.out::println); // display each Pair

  }

}
jpvee
источник
Проблема в том, что это выбрасывает безгражданство в окно.
Роб
@ Роб, а в чем тут проблема?
Александр Дубинский
Один из основных моментов лямбда - отсутствие изменяемого состояния, чтобы внутренние интеграторы могли распараллеливать работу.
Роб
@Rob: Да, вы правы, но данный пример потока все равно не поддается параллелизму, поскольку каждый элемент (кроме первого и последнего) используется как первый и второй элементы некоторой пары.
jpvee
@jpvee да, я подумал, что вы так думаете. Я задаюсь вопросом, нет ли способа сделать это с помощью другого картографа. По сути, все, что вам нужно, это эквивалент того, чтобы инкрементер цикла шел по два, а затем функтор принимал 2 аргумента. Это должно быть возможно.
Роб
0

Для вычисления последовательных разностей во время (X-значение) из временных рядов, я использую stream«ы collect(...)метода:

final List< Long > intervals = timeSeries.data().stream()
                    .map( TimeSeries.Datum::x )
                    .collect( DifferenceCollector::new, DifferenceCollector::accept, DifferenceCollector::combine )
                    .intervals();

Где DifferenceCollector выглядит примерно так:

public class DifferenceCollector implements LongConsumer
{
    private final List< Long > intervals = new ArrayList<>();
    private Long lastTime;

    @Override
    public void accept( final long time )
    {
        if( Objects.isNull( lastTime ) )
        {
            lastTime = time;
        }
        else
        {
            intervals.add( time - lastTime );
            lastTime = time;
        }
    }

    public void combine( final DifferenceCollector other )
    {
        intervals.addAll( other.intervals );
        lastTime = other.lastTime;
    }

    public List< Long > intervals()
    {
        return intervals;
    }
}

Возможно, вы могли бы изменить это в соответствии со своими потребностями.

Роб Филипп
источник
0

Я, наконец, нашел способ обмануть Stream.reduce, чтобы иметь возможность аккуратно работать с парами значений; есть множество вариантов использования, которые требуют этого средства, которое не появляется естественным образом в JDK 8:

public static int ArithGeo(int[] arr) {
    //Geometric
    List<Integer> diffList = new ArrayList<>();
    List<Integer> divList = new ArrayList<>();
    Arrays.stream(arr).reduce((left, right) -> {
        diffList.add(right-left);
        divList.add(right/left);
        return right;
    });
    //Arithmetic
    if(diffList.stream().distinct().count() == 1) {
        return 1;
    }
    //Geometric
    if(divList.stream().distinct().count() == 1) {
        return 2;
    }
    return -1;
}

Уловка, которую я использую, - это право возврата; заявление.

Beezer
источник
1
Я не думаю, что reduceдает достаточно гарантий, чтобы это работало.
Александр Дубинский
Было бы интересно узнать больше о достаточных гарантиях . Не могли бы вы уточнить? Может быть, в Guava есть альтернатива ... но я ограничен и не могу ее использовать.
Beezer
-1

Элегантным решением будет использование zip . Что-то вроде:

List<Integer> input = Arrays.asList(0, 1, 2, 3, 4);
Stream<Pair> pairStream = Streams.zip(input.stream(),
                                      input.stream().substream(1),
                                      (a, b) -> new Pair(a, b)
);

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

Другая (гораздо более неприятная) проблема заключается в том, что zip вместе со всем классом Streams недавно был удален из API. Приведенный выше код работает только с версиями b95 или более ранними. Итак, с последним JDK я бы сказал, что нет элегантного решения в стиле FP, и сейчас мы можем просто надеяться, что каким-то образом zip будет повторно введен в API.

гаджет
источник
Действительно, zipбыл удален. Я не помню все , что было на Streamsклассе, но некоторые вещи , которые мигрировали быть статическими методами на Streamинтерфейсе, а также есть StreamSupportи Stream.Builderклассы.
Стюарт Маркс
Это правильно. Некоторые другие методы, такие как concat или iterate, были перемещены и стали методами по умолчанию в Stream. К сожалению, zip только что удалили из API. Я понимаю причины этого выбора (например, отсутствие кортежей), но все же это была хорошая функция.
гаджет
2
@gadget При чем тут кортежи zip? Какая бы педантичная причина ни была изобретена, убийство не оправдывает zip.
Александр Дубинский
@AleksandrDubinsky В большинстве случаев zip используется для создания коллекции пар / кортежей на выходе. Они утверждали, что, если они сохранят zip, люди также будут просить кортежи как часть JDK. Однако я бы никогда не удалил существующую функцию.
гаджет
-1

Это интересная проблема. Подходит ли моя гибридная попытка ниже?

public static void main(String[] args) {
    List<Integer> list = Arrays.asList(1, 2, 3);
    Iterator<Integer> first = list.iterator();
    first.next();
    if (first.hasNext())
        list.stream()
        .skip(1)
        .map(v -> new Pair(first.next(), v))
        .forEach(System.out::println);
}

Я считаю, что он не поддается параллельной обработке и, следовательно, может быть дисквалифицирован.

Кедар Мхасваде
источник
Вопрос не требовал параллельной обработки, но предполагал, что у нас есть только файл, а Streamне файл List. Конечно, мы также можем извлечь итератор из Stream, так что это может быть правильным решением. Тем не менее, это оригинальный подход.
Александр Дубинский
-1

Как отмечали другие, из-за природы проблемы требуется некоторая сохранность состояния.

Я столкнулся с аналогичной проблемой, в которой я хотел, по сути, функцию LEAD Oracle SQL. Моя попытка реализовать это ниже.

/**
 * Stream that pairs each element in the stream with the next subsequent element.
 * The final pair will have only the first item, the second will be null.
 */
<T> Spliterator<Pair<T>> lead(final Stream<T> stream)
{
    final Iterator<T> input = stream.sequential().iterator();

    final Iterable<Pair<T>> iterable = () ->
    {
        return new Iterator<Pair<T>>()
        {
            Optional<T> current = getOptionalNext(input);

            @Override
            public boolean hasNext()
            {
                return current.isPresent();
            }

            @Override
            public Pair<T> next()
            {
                Optional<T> next = getOptionalNext(input);
                final Pair<T> pair = next.isPresent()
                    ? new Pair(current.get(), next.get())
                    : new Pair(current.get(), null);
                current = next;

                return pair;
            }
        };
    };

    return iterable.spliterator();
}

private <T> Optional<T> getOptionalNext(final Iterator<T> iterator)
{
    return iterator.hasNext()
        ? Optional.of(iterator.next())
        : Optional.empty();
}
Hoons
источник
-1

Вы можете добиться этого, используя ограниченную очередь для хранения элементов, которые проходят через поток (что основано на идее, которую я подробно описал здесь: возможно ли получить следующий элемент в потоке? )

В примере ниже сначала определяется экземпляр класса BoundedQueue, который будет хранить элементы, проходящие через поток (если вам не нравится идея расширения LinkedList, обратитесь к упомянутой выше ссылке для альтернативного и более общего подхода). Позже вы просто объедините два последующих элемента в экземпляр Pair:

public class TwoSubsequentElems {
  public static void main(String[] args) {
    List<Integer> input = new ArrayList<Integer>(asList(0, 1, 2, 3, 4));

    class BoundedQueue<T> extends LinkedList<T> {
      public BoundedQueue<T> save(T curElem) {
        if (size() == 2) { // we need to know only two subsequent elements
          pollLast(); // remove last to keep only requested number of elements
        }

        offerFirst(curElem);

        return this;
      }

      public T getPrevious() {
        return (size() < 2) ? null : getLast();
      }

      public T getCurrent() {
        return (size() == 0) ? null : getFirst();
      }
    }

    BoundedQueue<Integer> streamHistory = new BoundedQueue<Integer>();

    final List<Pair<Integer>> answer = input.stream()
      .map(i -> streamHistory.save(i))
      .filter(e -> e.getPrevious() != null)
      .map(e -> new Pair<Integer>(e.getPrevious(), e.getCurrent()))
      .collect(Collectors.toList());

    answer.forEach(System.out::println);
  }
}
Walkeros
источник
-3

Я согласен с @aepurniet, но вместо карты вы должны использовать mapToObj

range(0, 100).mapToObj((i) -> new Pair(i, i+1)).forEach(System.out::println);
user190364
источник
1
Правильно. Но это просто собирает пары целых чисел, а не пары элементов потока (любого типа).
Александр Дубинский
-5

Запустите forцикл от 0 до length-1вашего потока

for(int i = 0 ; i < stream.length-1 ; i++)
{
    Pair pair = new Pair(stream[i], stream[i+1]);
    // then add your pair to an array
}
Savv
источник
3
а где лямбда-часть решения?
Olimpiu POP
Это не тот случай, когда поток бесконечен
mishadoff
@Olimpiu - Откуда у тебя лямбда была обязательной? Я дважды прочитал вопрос, чтобы убедиться, что я его не пропустил. Я также проверил историю правок. И вопрос не связан с этим.
jww