it-swarm.com.ru

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

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

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

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

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

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

87
Aleksandr Dubinsky

Моя библиотека 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);

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

30
Tagir Valeev

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

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

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

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

64
Stuart Marks

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

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)

15
mishadoff

Я реализовал оболочку сплитератора, которая берет все элементы nT из исходного сплитератора и создает 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);
14
Tomek Rękawek

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

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

  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 может принимать коллекцию с 2 элементами.

Если вы хотите сгруппировать по 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 от потрясающего jOOλ (который расширяет Java.util.stream.Stream), поэтому решения, представленные Lukas, также будут работать с любым типом Stream. Для всех, кто интересуется, основные различия между операторами окна/скольжения заключаются в очевидном компромиссе между мощностью и сложностью и пригодностью для использования с бесконечными потоками (скольжение не потребляет поток, а буферизирует при его передаче).

6
John McClean

библиотека 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
4
Alexis C.

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

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

4
Lukas Eder

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

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

2
frhack

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

  }

}
0
jpvee

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

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

0
Beezer

Для расчета последовательных различий во времени (значения x) временного ряда я использую метод stream's 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
Rob Philipp

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