스트림을 두 개의 스트림으로 분할 할 수 있습니까?
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를 사용하십시오 .
이렇게하면 Map
from 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)
방법을 사용할 수 없습니다. 컬렉터 팩토리없이 수동으로 수행해야합니다. 구현은 다음과 같습니다.
IntStream intStream = IntStream.iterate(0, i -> i + 1).limit(1000000);
Predicate<Integer> p = x -> r.nextBoolean();
Map<Boolean, List<Integer>> groups = intStream.collect(() -> {
Map<Boolean, List<Integer>> map = new HashMap<>();
map.put(false, new ArrayList<>());
map.put(true, new ArrayList<>());
return map;
}, (map, x) -> {
boolean partition = p.test(x);
List<Integer> list = map.get(partition);
list.add(x);
}, (map1, map2) -> {
map1.get(false).addAll(map2.get(false));
map1.get(true).addAll(map2.get(true));
});
System.out.println(groups.get(false).size());
System.out.println(groups.get(true).size());
편집하다
As pointed out, the above 'workaround' is not thread safe. Converting to a normal Stream
before collecting is the way to go:
Stream<Integer> stream = intStream.boxed();
Unfortunately, what you ask for is directly frowned upon in the JavaDoc of Stream:
A stream should be operated on (invoking an intermediate or terminal stream operation) only once. This rules out, for example, "forked" streams, where the same source feeds two or more pipelines, or multiple traversals of the same stream.
You can work around this using peek
or other methods should you truly desire that type of behaviour. In this case, what you should do is instead of trying to back two streams from the same original Stream source with a forking filter, you would duplicate your stream and filter each of the duplicates appropriately.
However, you may wish to reconsider if a Stream
is the appropriate structure for your use case.
I stumbled across this question to my self and I feel that a forked stream has some use cases that could prove valid. I wrote the code below as a consumer so that it does not do anything but you could apply it to functions and anything else you might come across.
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);
}
}
}
Now your code implementation could be something like this:
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")));
This is against the general mechanism of Stream. Say you can split Stream S0 to Sa and Sb like you wanted. Performing any terminal operation, say count()
, on Sa will necessarily "consume" all elements in S0. Therefore Sb lost its data source.
Previously, Stream had a tee()
method, I think, which duplicate a stream to two. It's removed now.
Stream has a peek() method though, you might be able to use it to achieve your requirements.
not exactly, but you may be able to accomplish what you need by invoking Collectors.groupingBy()
. you create a new Collection, and can then instantiate streams on that new collection.
This was the least bad answer I could come up with.
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);
}
}
This takes a stream of integers and splits them at 5. For those greater than 5 it filters only even numbers and puts them in a list. For the rest it joins them with |.
outputs:
([6, 8],0|1|2|3|4|5)
Its not ideal as it collects everything into intermediary collections breaking the stream (and has too many arguments!)
I stumbled across this question while looking for a way to filter certain elements out of a stream and log them as errors. So I did not really need to split the stream so much as attach a premature terminating action to a predicate with unobtrusive syntax. This is what I came up with:
public class MyProcess {
/* Return a Predicate that performs a bail-out action on non-matching items. */
private static <T> Predicate<T> withAltAction(Predicate<T> pred, Consumer<T> altAction) {
return x -> {
if (pred.test(x)) {
return true;
}
altAction.accept(x);
return false;
};
/* Example usage in non-trivial pipeline */
public void processItems(Stream<Item> stream) {
stream.filter(Objects::nonNull)
.peek(this::logItem)
.map(Item::getSubItems)
.filter(withAltAction(SubItem::isValid,
i -> logError(i, "Invalid")))
.peek(this::logSubItem)
.filter(withAltAction(i -> i.size() > 10,
i -> logError(i, "Too large")))
.map(SubItem::toDisplayItem)
.forEach(this::display);
}
}
How about:
Supplier<Stream<Integer>> randomIntsStreamSupplier =
() -> (new Random()).ints(0, 2).boxed();
Stream<Integer> tails =
randomIntsStreamSupplier.get().filter(x->x.equals(0));
Stream<Integer> heads =
randomIntsStreamSupplier.get().filter(x->x.equals(1));
참고URL : https://stackoverflow.com/questions/19940319/can-you-split-a-stream-into-two-streams
'Programing' 카테고리의 다른 글
MySQL 4.0에서 생성 및 마지막 업데이트 타임 스탬프 열 모두 (0) | 2020.07.12 |
---|---|
SQL Server에 대한 Maven 종속성 설정 (0) | 2020.07.12 |
SQL Server : SQL 쿼리를 사용하여 테이블 기본 키 가져 오기 (0) | 2020.07.12 |
플로트 오른쪽과 위치 절대가 함께 작동하지 않습니다 (0) | 2020.07.12 |
ZSH의 PATH 변수에 새 항목 추가 (0) | 2020.07.12 |