[java] 스트림에서 연속 쌍 수집

스트림을 감안할 때 등 { 0, 1, 2, 3, 4 },

어떻게하면 그것을 주어진 형태로 가장 우아하게 바꿀 수 있습니까?

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

(물론 내가 Pair 클래스를 정의했다고 가정)?

편집 : 이것은 int 또는 원시 스트림에 관한 것이 아닙니다. 모든 유형의 스트림에 대한 대답은 일반적이어야합니다.



답변

표준 스트림을 확장하는 My 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 벤치 마크입니다 ( 질문 참조 ).


답변

Java 8 스트림 라이브러리는 주로 병렬 처리를 위해 스트림을 더 작은 청크로 분할하는 데 맞춰져 있으므로 상태 저장 파이프 라인 단계는 매우 제한적이며 현재 스트림 요소의 인덱스를 가져오고 인접한 스트림 요소에 액세스하는 것과 같은 작업은 지원되지 않습니다.

물론 이러한 문제를 해결하는 일반적인 방법은 몇 가지 제한 사항이 있지만 인덱스로 스트림을 구동하고 요소를 검색 할 수있는 ArrayList와 같은 일부 임의 액세스 데이터 구조에서 처리되는 값에 의존하는 것입니다. 값이에 있으면 arrayList다음과 같이 요청한대로 쌍을 생성 할 수 있습니다.

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

물론 제한은 입력이 무한 스트림이 될 수 없다는 것입니다. 하지만이 파이프 라인은 병렬로 실행할 수 있습니다.


답변

이것은 우아하지 않고 hackish 솔루션이지만 무한 스트림에서 작동합니다.

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

추신 : 클로저와 같은 더 나은 해결책이 있기를 바랍니다.(partition 2 1 stream)


답변

원래 분할기에서 모든 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);


답변

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


답변

슬라이딩 연산자를 사용하여 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 생성자가 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은 멋진 jOOλ (java.util.stream.Stream 확장)에서 Seq를 확장하는 ReactiveSeq를 확장하므로 Lukas가 제공하는 솔루션은 Stream 유형에서도 작동합니다. 관심있는 사람을 위해 창 / 슬라이딩 연산자 간의 주요 차이점은 명백한 상대적 전력 / 복잡도 절충 및 무한 스트림 사용에 대한 적합성입니다.


답변

양성자 팩 라이브러리는 윈도우 된 functionnality을 제공합니다. 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