[java] 스트림을 두 개의 스트림으로 분할 할 수 있습니까?

Java 8 스트림으로 표시되는 데이터 세트가 있습니다.

Stream<T> stream = ...;

임의의 하위 집합을 얻기 위해 필터링하는 방법을 볼 수 있습니다-예를 들어

Random r = new Random();
PrimitiveIterator.OfInt coin = r.ints(0, 2).iterator();
Stream<T> heads = stream.filter((x) -> (coin.nextInt() == 0));

또한이 스트림을 줄여 데이터 세트의 임의의 절반을 나타내는 두 개의 목록을 얻은 다음 다시 스트림으로 변환하는 방법을 알 수 있습니다. 그러나 초기 스트림에서 두 개의 스트림을 생성하는 직접적인 방법이 있습니까? 같은 것

(heads, tails) = stream.[some kind of split based on filter]

통찰력을 가져 주셔서 감사합니다.



답변

정확히. Stream하나에서 두 개를 얻을 수는 없습니다 . 이것은 이해가되지 않습니다. 동시에 다른 하나를 생성 할 필요없이 하나를 어떻게 반복 하시겠습니까? 스트림은 한 번만 조작 할 수 있습니다.

그러나 목록이나 무언가에 덤프하려는 경우 할 수 있습니다

stream.forEach((x) -> ((x == 0) ? heads : tails).add(x));


답변

콜렉터 이 사용될 수있다.

  • 두 가지 범주의 경우 Collectors.partitioningBy()factory를 사용하십시오 .

이렇게하면 Mapfrom Boolean을 만들고를 기준으로 List항목을 하나 또는 다른 목록에 넣습니다 Predicate.

참고 : 스트림 전체를 소비해야하므로 무한 스트림에서는 작동하지 않습니다. 스트림은 어쨌든 소비되기 때문에이 방법을 사용하면 메모리로 새로운 스트림을 만드는 대신 단순히 목록에 넣습니다. 출력으로 스트림이 필요한 경우 언제든지 해당 목록을 스트리밍 할 수 있습니다.

또한 사용자가 제공 한 헤드 전용 예제에서도 반복자가 필요하지 않습니다.

  • 이진 분할은 다음과 같습니다.
Random r = new Random();

Map<Boolean, List<String>> groups = stream
    .collect(Collectors.partitioningBy(x -> r.nextBoolean()));

System.out.println(groups.get(false).size());
System.out.println(groups.get(true).size());
  • 더 많은 카테고리는 Collectors.groupingBy()공장을 사용하십시오 .
Map<Object, List<String>> groups = stream
    .collect(Collectors.groupingBy(x -> r.nextInt(3)));
System.out.println(groups.get(0).size());
System.out.println(groups.get(1).size());
System.out.println(groups.get(2).size());

스트림이 아닌 Stream기본 스트림 중 하나 인 IntStream경우이 .collect(Collectors)방법을 사용할 수 없습니다. 컬렉터 팩토리없이 수동으로 수행해야합니다. 구현은 다음과 같습니다.

[2020-04-16 이후의 예제 2.0]

    IntStream    intStream = IntStream.iterate(0, i -> i + 1).limit(100000).parallel();
    IntPredicate predicate = ignored -> r.nextBoolean();

    Map<Boolean, List<Integer>> groups = intStream.collect(
            () -> Map.of(false, new ArrayList<>(100000),
                         true , new ArrayList<>(100000)),
            (map, value) -> map.get(predicate.test(value)).add(value),
            (map1, map2) -> {
                map1.get(false).addAll(map2.get(false));
                map1.get(true ).addAll(map2.get(true ));
            });

이 예제에서는 초기 컬렉션의 전체 크기로 ArrayLists를 초기화합니다 (이것이 전혀 알려지지 않은 경우). 이렇게하면 최악의 시나리오에서도 크기 조정 이벤트를 방지 할 수 있지만 2 * N * T 공간을 잠재적으로 증가시킬 수 있습니다 (N = 초기 요소 수, T = 스레드 수). 속도를 위해 공간을 절충하기 위해 한 파티션에서 예상되는 가장 많은 수의 요소 (일반적으로 균형 잡힌 분할의 경우 N / 2 이상)와 같이 공간을 그대로 두거나 가장 잘 교육 된 추측을 사용할 수 있습니다.

Java 9 메소드를 사용하여 다른 사람을 화나게하지 않기를 바랍니다. Java 8 버전의 경우 편집 히스토리를보십시오.


답변

나는이 질문을 내 자신으로 우연히 발견했으며 분기 된 스트림에는 유효한 것으로 입증 된 유스 케이스가 있다고 생각합니다. 나는 아래 코드를 소비자로 작성하여 아무것도하지 않지만 함수 및 다른 것들에 적용 할 수 있습니다.

class PredicateSplitterConsumer<T> implements Consumer<T>
{
  private Predicate<T> predicate;
  private Consumer<T>  positiveConsumer;
  private Consumer<T>  negativeConsumer;

  public PredicateSplitterConsumer(Predicate<T> predicate, Consumer<T> positive, Consumer<T> negative)
  {
    this.predicate = predicate;
    this.positiveConsumer = positive;
    this.negativeConsumer = negative;
  }

  @Override
  public void accept(T t)
  {
    if (predicate.test(t))
    {
      positiveConsumer.accept(t);
    }
    else
    {
      negativeConsumer.accept(t);
    }
  }
}

이제 코드 구현은 다음과 같습니다.

personsArray.forEach(
        new PredicateSplitterConsumer<>(
            person -> person.getDateOfBirth().isPresent(),
            person -> System.out.println(person.getName()),
            person -> System.out.println(person.getName() + " does not have Date of birth")));


답변

불행히도, 당신이 요구하는 것은 JavaDoc of Stream 에서 직접 찌푸 립니다 .

스트림은 한 번만 작동해야합니다 (중간 또는 터미널 스트림 작동 호출). 예를 들어, 동일한 소스가 둘 이상의 파이프 라인 또는 동일한 스트림의 여러 순회를 공급하는 “포크 (forked)”스트림을 제외합니다.

이러한 peek유형의 동작을 진정으로 원한다면이 방법을 사용 하거나 다른 방법을 사용할 수 있습니다 . 이 경우해야 할 일은 포크 필터를 사용하여 동일한 원본 스트림 소스에서 두 개의 스트림을 백업하는 대신 스트림을 복제하고 각 중복 항목을 적절하게 필터링하는 것입니다.

그러나 Stream사용 사례에 적합한 구조 인 경우 재고를 다시 고려할 수 있습니다.


답변

이것은 스트림의 일반적인 메커니즘에 위배됩니다. 원하는대로 스트림 S0을 Sa와 Sb로 분할 할 수 있다고 가정합니다. 터미널 작업 수행count() 를 들어 Sa에서 하면 반드시 S0의 모든 요소를 ​​”소비”해야합니다. 따라서 Sb는 데이터 소스를 잃었습니다.

이전에는 스트림에 tee() 에 스트림을 두 개로 복제하는 방법이 있다고 생각했습니다. 이제 제거되었습니다.

Stream에는 peek () 메서드가 있지만이를 사용하여 요구 사항을 달성 할 수 있습니다.


답변

정확하게는 아니지만 호출하여 필요한 것을 달성 할 수 있습니다 Collectors.groupingBy(). 새 컬렉션을 만든 다음 해당 새 컬렉션에서 스트림을 인스턴스화 할 수 있습니다.


답변

이것은 내가 생각해 낼 수있는 가장 나쁜 대답이었습니다.

import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;

public class Test {

    public static <T, L, R> Pair<L, R> splitStream(Stream<T> inputStream, Predicate<T> predicate,
            Function<Stream<T>, L> trueStreamProcessor, Function<Stream<T>, R> falseStreamProcessor) {

        Map<Boolean, List<T>> partitioned = inputStream.collect(Collectors.partitioningBy(predicate));
        L trueResult = trueStreamProcessor.apply(partitioned.get(Boolean.TRUE).stream());
        R falseResult = falseStreamProcessor.apply(partitioned.get(Boolean.FALSE).stream());

        return new ImmutablePair<L, R>(trueResult, falseResult);
    }

    public static void main(String[] args) {

        Stream<Integer> stream = Stream.iterate(0, n -> n + 1).limit(10);

        Pair<List<Integer>, String> results = splitStream(stream,
                n -> n > 5,
                s -> s.filter(n -> n % 2 == 0).collect(Collectors.toList()),
                s -> s.map(n -> n.toString()).collect(Collectors.joining("|")));

        System.out.println(results);
    }

}

이것은 정수 스트림을 취하여 5로 나눕니다. 5보다 큰 숫자의 경우 짝수 만 필터링하여 목록에 넣습니다. 나머지는 |와 결합합니다.

출력 :

 ([6, 8],0|1|2|3|4|5)

스트림을 깨는 중간 컬렉션으로 모든 것을 수집하기 때문에 이상적이지 않습니다 (논쟁이 너무 많습니다!)