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

Для потока, такого как { 0, 1, 2, 3, 4 },

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

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

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

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

Ответ 1

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

Ответ 2

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

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

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

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

Ответ 3

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

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

П.С. Я надеюсь, что есть лучшее решение, например, clojure (partition 2 1 stream)

Ответ 4

Я реализовал оболочку spliterator, которая берет каждые n элементы T из исходного spliterator и производит 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);

Ответ 5

Вы можете сделать это с помощью метода 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;
}

Ответ 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);

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

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

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

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

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

Примечание: для однопоточной операции ReactiveSeq будет более подходящим. LazyFutureStream расширяет ReactiveSeq, но в основном предназначен для параллельного/параллельного использования (это поток фьючерсов).

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

Ответ 7

протонная библиотека предоставляет оконную функцию. Учитывая класс 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

Ответ 8

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

Если вы хотите использовать стороннюю библиотеку и не нуждаетесь в parallelism, то 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-кортежей

Вопрос в комментариях задавал более общее решение, в котором должны быть собраны не пары, а 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λ

Ответ 9

Мы можем использовать 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();
        });

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

Ответ 10

Операция по существу с точки зрения состояния, поэтому на самом деле не те потоки, которые предназначены для решения - см. раздел "Безгосударственные поведения" в 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 + '}'; }
}

Ответ 12

В вашем случае я бы написал свой пользовательский 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

  }

}

Ответ 13

Для вычисления последовательных различий во времени (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;
    }
}

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

Ответ 14

Я наконец-то нашел способ обмануть 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;
}

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

Ответ 15

Элегантным решением было бы использовать 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.

Ответ 16

Это интересная проблема. Является ли моя гибридная попытка ниже любой пользы?

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

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

Ответ 17

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

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

/**
 * 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();
}

Ответ 18

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

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

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

Ответ 19

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

range(0, 100).mapToObj((i) -> new Pair(i, i+1)).forEach(System.out::println);

Ответ 20

Запустите цикл 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
}