Programing

스트림에서 연속 쌍 수집

lottogame 2020. 9. 3. 23:39
반응형

스트림에서 연속 쌍 수집


스트림을 감안할 때 등 { 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

연속 쌍 찾기

타사 라이브러리를 사용하고 병렬 처리가 필요하지 않은 경우 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λ 뒤에있는 회사에서 일합니다.


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

버퍼 연산자 피 감시로 방출한다 항목 방사하는 이러한 항목들의 집합을 버퍼링한다는 것을 피 감시 변환 ..


작업은 본질적으로 상태 저장이므로 실제로 해결하려는 스트림이 아닙니다. javadoc 의 "Stateless Behaviors"섹션을 참조하십시오 .

가장 좋은 방법은 상태 저장 동작 매개 변수를 피하여 작업을 완전히 스트리밍하는 것입니다.

여기에서 한 가지 해결책은 외부 카운터를 통해 스트림에 상태를 도입하는 것입니다.하지만 순차 스트림에서만 작동합니다.

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

Streams.zip(..)의존하는 사람들을 위해 구아바 에서 사용할 수 있습니다 .

예:

Streams.zip(list.stream(),
            list.stream().skip(1),
            (a, b) -> System.out.printf("%s %s\n", a, b));

귀하의 경우에는 전달 된 마지막 int를 추적하는 사용자 지정 IntFunction을 작성하고이를 사용하여 원래 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

  }

}

시계열의 시간 (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;
    }
}

필요에 맞게 수정할 수 있습니다.


나는 마침내 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;
}

내가 사용하는 트릭은 바로 반환입니다. 성명서.


우아한 해결책은 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)
);

This is pretty concise and elegant, however it uses a list as an input. An infinite stream source cannot be processed this way.

Another (lot more troublesome) issue is that zip together with the entire Streams class has been lately removed from the API. The above code only works with b95 or older releases. So with the latest JDK I would say there is no elegant FP style solution and right now we can just hope that in some way zip will be reintroduced to the API.


This is an interesting problem. Is my hybrid attempt below any good?

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

I believe it does not lend itself to parallel processing, and hence may be disqualified.


As others have observed, there is, due to the nature of the problem, some statefulness required.

I was faced with a similar problem, in which I wanted what was essentially the Oracle SQL function LEAD. My attempt to implement that is below.

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

You can achieve that by using a bounded queue to store elements which flows through the stream (which is basing on the idea which I described in detail here: Is it possible to get next element in the Stream?)

Belows example first defines instance of BoundedQueue class which will store elements going through the stream (if you don't like idea of extending the LinkedList, refer to link mentioned above for alternative and more generic approach). Later you just combine two subsequent elements into instance of 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);
  }
}

I agree with @aepurniet but instead map you have to use mapToObj

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

Run a for loop that runs from 0 to length-1 of your stream

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
}

참고URL : https://stackoverflow.com/questions/20470010/collect-successive-pairs-from-a-stream

반응형