How to buffer and group elements in Reactor Flux in Java - java

Given an infinite flux of objects, where each object has an ID, how can I use flux to create a buffered list of updates grouped by ID property (keeping the last emitted value)?
Thanks
Example
Obj(ID=A, V=1)
Obj(ID=A, V=2)
Obj(ID=B, V=3)
--- buffer -> I want to subscribe with a list of [Obj(ID=A, V=2), Obj(ID=B, V=3)]
Obj(ID=A, V=1)
Obj(ID=B, V=4)
Obj(ID=B, V=6)
Obj(ID=A, V=2)
--- buffer -> I want to subscribe with a list of [Obj(ID=B, V=6), Obj(ID=A, V=2)]
Obj(ID=B, V=1)
--- buffer -> I want to subscribe with a list of [Obj(ID=B, V=1)]
Something like the following would be perfect but it seems to wait the end of the flux in my tests instead of buffering.
flux
.buffer(Duration.ofMillis(2000))
.groupBy(Obj::getId)
.flatMap(GroupedFlux::getLast)
.collectToList()
.subscribe(this::printList);
It works with buffer and custom logic for grouping
public static void main(String[] args) {
flux.buffer(Duration.ofMillis(2000)).subscribe(this::groupList);
}
private void groupList(List<T> ts) {
Collection<T> values = ts.stream()
.collect(Collectors.toMap(T::getId, Function.identity(), (k, v) -> v))
.values();
System.out.println(values);
}

buffer will emit List<T>, therefore you could just use non-reactive java to group by. For example, java streams like in your example. Assuming your process function is reactive, you could continue the flow
flux
.buffer(Duration.ofMillis(2000))
.map(list ->
list.stream()
.collect(Collectors.toMap(Entry::getId, Function.identity(), (k, v) -> v)))
.flatMapIterable(Map::values)
.flatMap(obj -> process(obj));

I was able to achieve it with the reactive grouping
flux.window(Duration.ofMillis(2000))
.flatMap(window -> window.groupBy(Entry::getId)
.flatMap(GroupedFlux::last)
.collectList()
)
.subscribe(this::printList);

Related

Java 8 Streams - Call "map" during stream operations without saving to variable

I could not find many answers to this, though I suppose its not possible, but I was curious - is there anyway in Java8 streams to "refer" or to "call" the HashMap that is created via the .collectors(Collectors.toMap(key -> "key", val -> "val")? I know I can save this resultant reduction which creates the Map to Map<String, String> myMap variable, but I still had to perform operations on this resultant HashMap and I wanted to keep the stream going, so I wanted to do something like this:
myHashSet.stream().
.filter(i -> i != null)
.collectors(Collectors.toMap(key -> "key", val -> "val")
.forEach((k,v) -> {
// HERE IS WHERE I WANT TO CALL THE MAP CREATED ABOVE
if( MAP.contains("someRandomValue") {
}
}));
I'm assuming you can't do this, but I was hoping there was some method or something so I don't have to "kill" the stream, i.e. save the Map to a variable, then proceed to stream it out again etc...
The collect operation in Stream is a terminal operation. So there is no Stream after collect. You would have to do something like below.
var myMap = myHashSet.stream().
.filter(i -> i != null)
.collectors(Collectors.toMap(key -> "key", val -> "val");
myMap.forEach((k,v) -> {
// HERE IS WHERE I WANT TO CALL THE MAP CREATED ABOVE
if( MAP.contains("someRandomValue") {
}
}));
Below article describes about the available intermediate operations in Stream.
https://www.javacodegeeks.com/2020/04/java-8-stream-intermediate-operations-methods-examples.html
I solved this with #shmosel's help using collectingAndThen. This was the closest to what I wanted to do.
myArrayList.stream().
.collect(Collectors.collectingAndThen(Collectors.toMap(key -> key.getName(), val -> val), map -> map.forEach((key, val) -> System.out.println(map.contains(key)));

handle value by reactor with stream

I just want to do something like this by reactor
Object value=1;
List<Consumer>consumers=new ArrayList();
consumers.add(xx) .....
for (Consumer c : consumers)
{
if(c.consumer(value)){ // dosomething}
else return
}
How could i implement this by the reactive
Thanks
Use fromIterable to create a Flux that emits each element from the iterable list:
Flux.fromIterable(consumers)
and takeWhile to relay consumers while your predicate returns true:
Flux.fromIterable(consumers)
.takeWhile(c -> <your predicate>)
.flatMap(c -> dosomethingAsync())
.subscribe();

Convert List<Mono<String>> to Flux<String>

There are few question , but there answers are very specific to some code.
Generally, how to convert a Stream of Mono to Flux
List<Mono<String> listOfMono = stream()
.map( s -> { do something and return Mono<String> } )
.collect(Collectors.toList());
How to convert listOfMono object to Flux<String>
You can use fromIterable and then use flatMap to flatten the Mono
Transform the elements emitted by this Flux asynchronously into Publishers, then flatten these inner publishers into a single Flux through merging, which allow them to interleave.
Flux<String> result = Flux.fromIterable(listOfMono)
.flatMap(Function.identity());
If your input is a list of Monos you can simply do:
Flux.merge(listOfMono);
If your input is stream you can either do
stream()
.map( s -> { do something and return Mono<String> } )
.collect(Collectors.collectingAndThen(Collectors.toList(), Flux::merge));
OR
Flux.fromStream(stream())
.flatMap( s -> { do something and return Mono<String> } )
I'd personally prefer the last option as that is the most straighforward and idiomatic.

Java stream: Collect Stream<int[]> to List<int[]>

I try to get my head around java streams.
Atm I have this construct which does not work:
List<int[]> whiteLists = processes.stream()
.map(Process::getEventList)
.forEach(eventList -> eventList.stream()
.map(event -> event.getPropertie("whitelist"))
.map(propertie -> propertie.getIntArray())
.collect(Collectors.toList()));
}
The hierarchy is:
Process
Event
Property
Process::getEventList returns a list of Event objects
event.getPropertie("whitelist") returns a Property objects which hast the method getIntArray()
event.getPropertie() gives me an int-array.
How do I collect these array into a List of arrays?
Thanks!
You can't use forEach() as it takes a Consumer, meaning it will consume the stream, but can't return anything (so nothing to collect).
You need flatMap to stream the internal eventList as follows
List<int[]> whiteLists = processes.stream()
.flatMap(p -> p.getEventList().stream())
.map(event -> event.getPropertie("whitelist"))
.map(propertie -> propertie.getIntArray())
.collect(Collectors.toList());

terminal stream operation, which takes map as an input in Java 8

Do we have some terminal function which takes a function as an input? I have an inner loop, which returns a response, as put in the below example. Rather than putting the response in outside list, I wanted inner loop to collect and send the response.
List<A> consignments = Lists.emptyList();
IntStream.range(0, days)
.mapToObj(startDate::plusDays)
.forEach(day -> IntStream.range(0, hours)
.forEach(hour -> consignments.add(myfunction()))
);
I wanted it something like:
List<A> consignments = IntStream.range(0, days)
.mapToObj(startDate::plusDays)
.forEach(day -> IntStream.range(0, hours)
.mapToObj(hour -> myFunction())
.collect(toList()));
Since forEach is a consumer, this won't work as it is, so do we have some easy way to achieve it, which I am missing?
If I understand your intention correctly, flatMap should work:
List<A> consignments =
IntStream.range(0, days)
.mapToObj(startDate::plusDays)
.flatMap(day -> IntStream.range(0, hours)
.mapToObj(hour -> myFunction()))
.collect(toList());

Categories