2014-10-16 3 views
7

Stream<T>이 있으면 skip(long)을 사용하여 스트림의 처음 몇 요소를 건너 뛸 수 있습니다. 그러나 스트림의 끝에서 지정된 수의 요소를 건너 뛰는 것과 동일한 요소가없는 것으로 보입니다.스트림의 마지막 x 요소 건너 뛰기 <T>

가장 확실한 해결책은 limit(originalLength - elementsToRemoveAtEnd)을 사용하는 것입니다.하지만 그 전에는 항상 초기 길이를 알아야합니다. 이는 항상 그런 것은 아닙니다.

알 수없는 길이의 스트림의 마지막 몇 요소를 Collection에 수집하지 않고 제거하고 요소를 계산하여 다시 스트림하는 방법이 있습니까?

+4

그러한 방법이없는 이유는 아마 당신이주는 것입니다. 일반적으로 스트리밍 중 '스트림'이 종료되는시기를 알 수있는 방법이 없으므로 완료 될 때까지 마지막 몇 가지 요소를 실제로 제거 할 수 없습니다. – Keppil

+0

나머지 요소를 건너 뛰고 싶은 시점을 어떻게 결정합니까? 원래 스트림을 감싸는 자체 스트림을 구현할 수 없습니까? – user

+0

스트림의 길이를 모르는 경우 요소가 마지막임을 어떻게 알 수 있습니까? – assylias

답변

8

길이가 알려지지 않은 Stream에 대한 일반적인 저장 장치없는 해결 방법은 없습니다.

static <T> Stream<T> skipLastElements(Stream<T> s, int count) { 
    if(count<=0) { 
     if(count==0) return s; 
     throw new IllegalArgumentException(count+" < 0"); 
    } 
    ArrayDeque<T> pending=new ArrayDeque<T>(count+1); 
    Spliterator<T> src=s.spliterator(); 
    return StreamSupport.stream(new Spliterator<T>() { 
     public boolean tryAdvance(Consumer<? super T> action) { 
      while(pending.size()<=count && src.tryAdvance(pending::add)); 
      if(pending.size()>count) { 
       action.accept(pending.remove()); 
       return true; 
      } 
      return false; 
     } 
     public Spliterator<T> trySplit() { 
      return null; 
     } 
     public long estimateSize() { 
      return src.estimateSize()-count; 
     } 
     public int characteristics() { 
      return src.characteristics(); 
     } 
    }, false); 
} 
public static void main(String[] args) { 
    skipLastElements(Stream.of("foo", "bar", "baz", "hello", "world"), 2) 
    .forEach(System.out::println); 
} 
+0

위의 내용은 심각한 숨겨진 문제입니다! 'Stream.spliterator()'를 호출하면'StreamSpliterators.WrappingSpliterator'를 반환 할 수 있습니다. 'initPartialTraversalState()'메소드는 모든 스트림 요소를'Collection' (즉,'SpinedBuffer')에 저장합니다! 파일 입력을 기반으로 스트림의 힙을 날릴 수 있습니다. – Nathan

0

다음 코드는 n가 어디 n 요소를 버퍼 ArrayDeque를 사용하지만, 전체 스트림을 수집 할 필요가 없습니다 요소의 수는 건너 뛰려면, 당신은 단지 대규모로 저장이 필요 끝에 건너 뛸 요소 수. 트릭은 skip(n)을 사용하는 것입니다. 이로 인해 첫 번째 n 요소가 ArrayDeque에 추가됩니다. 그런 다음, n 요소가 버퍼링되면 스트림은 요소 처리를 계속하지만 ArrayDeque의 요소를 팝합니다. 스트림의 끝 부분에 도달하면 n 요소가 ArrayDeque에 붙어 버려지고 버려집니다.

ArrayDequenull 요소를 허용하지 않습니다. 아래 코드는 ArrayDeque에 추가하기 전에 nullNULL_VALUE에 매핑 한 다음 ArrayDeque에서 터지게 한 후 NULL_VALUEnull으로 다시 매핑합니다.

private static final Object NULL_VALUE = new Object(); 

public static <T> Stream<T> skipLast(Stream<T> input, int n)     
{ 
    ArrayDeque<T> queue; 

    if (n <= 0) 
     return(input); 

    queue = new ArrayDeque<>(n + 1); 

    input = input. 
     map(item -> item != null ? item : NULL_VALUE). 
     peek(queue::add). 
     skip(n). 
     map(item -> queue.pop()). 
     map(item -> item != NULL_VALUE ? item : null); 

    return(input); 
} 
+1

글쎄, 어떻게 스트림을 구현하는 방법에 따라, 스트림이 구현되는 방법에 따라 중단 될 수있는 솔루션에 비효율적 인 솔루션을 선호합니다. 당신의 솔루션은 현재의 구현이 스트림 소스와'skip '을 융합 할 수없는 것에 의존한다. 이후 버전에서는 Java 9의'count()'가 이러한 함수를 평가하지 못하는 것처럼 스트림 크기를 변경하지 않는 모든 이전 단계를 건너 뛸 수 있습니다. – Holger

+0

그 소문은 들었지만 그 최적화는 다음과 같이 제한됩니다. 애플리케이션의 범위 나 많은 코드가 깨지게됩니다. 'peek()'는 람다가 거의 항상 부작용을 가지기 때문에 최적화를 중단해야합니다. 그러므로'skip()'과'count()'는 그것을 최적화 할 수 없다. – Nathan

+0

Java 9의'count()'*는 이미 이러한 코드를 깨뜨 렸습니다. [자바 스트림은 디버깅을 위해서만 보입니다.] (https://stackoverflow.com/q/33635717/2711488)에서 이미 논의했듯이, 그 목적과는 다른 방법을 사용해서는 안됩니다. 이 메소드의 주된 목적은이 단계에서 요소가 처리되는지 여부를 디버그하는 것입니다. *이 메소드의 * 때문에 *이 단계에서 강제 처리하는 것은 무의미합니다.그러한 최적화의 존재를 감지하는 메소드를 사용할 수있을뿐만 아니라 Java 8 * 및 * Java 9에서 스트림이 필요 이상으로 많은 요소를 처리하는 것을 감지 할 수 있습니다. – Holger