RxJava: Return results from inner nested loops - java

I'm trying to use RxJava to iterate over 2 arrays and accumulate the results in the inner loop and eventually return a Single<Map>.
Here is a snippet of what I'm trying to achieve:
private Map<String, Collection<String>> processData(List<Organization> organizations, List<User> users) {
return Flowable.fromIterable(organizations) //
.flatMapSingle(organization-> Flowable.fromIterable(users) //
.filter(organization -> organization.exist(user)) //
.toMultimap(organization, user))
.blockingSingle();
}
I don't like the blockSingle call, is there a nicer way to handle this case?
EDIT
As suggested, I got rid of RxJava and use Java8 streams, as follow:
organizations.streams() //
.map(organization-> users.stream()
.filter(organization -> organization.exist(user))
.collect(MultimapCollector.toMultimap(user::role, user::id)));
The thing I can't understand is how eventually to get the result as Multimap<String,String>, currently it returns as Stream<Object>.

Why would you use RxJava for this task in the first place? There is no asynchronicity involved, so just use .filter()/.map()/.flatMap() from Java 8 (https://www.mkyong.com/java8/java-8-filter-a-map-examples/).

Related

Combine two streams and call method

I have a problem how to stream asynchornously and call a method,
e.g.
List<User> users = List.of(user1, user2, user3);
List<Workplace> worklpaces = List.of(workplace1,workplace2,workplace3)
It's always the same users.size == workplaces.size
we have a function mapping
public List<UserWithWorkplace> combineUserWithWorkplaceAndType(List<User> users,List<Workplace>
worklpaces, Type someRandomtype) {
//here is the problem it wont it should be get
//List<UserWithWorkplace>.size == users.size == workplaces.size
return users.stream().flatMap(user ->
worklpaces.stream()
.map(worklpace -> mapping(user,worklpace, someRandomtype)))
.toList()
}
private UserWithWorkplace mapping( User user, Workplace workplace,Type someRandomtype){
//cominging and returning user with workplace
}
How to achieve that result?
Assuming you want to create pairs of (user, workplace) from two separate users an workplaces streams, this operation is normally called "zipping".
Guava library provide Streams.zip(Stream, Steam, Function) method for this. In your case the code would look like:
Stream<UserWithWorkplace> zipped = Streams.zip(
users.stream(),
worklpaces.stream(),
(u, w) -> this.mapping(u, w, someRandomtype));
However your example code uses List and not Stream to represent data. I'm not sure if you have to use Java streams for this, a simple for loop with i index might be easier.
What you're describing is a zipping operation.
If using Google Guava, you can do this to combine them:
Streams.zip(users.stream(), workplaces.stream(), (user, workplace) -> mapping(user, workplace, someType))
You can also find some other implementations of this operation described here

Can I make this for loop and if statement more concise with a stream?

My code works fine but I want to get practice with Java 8 streams. I was wondering if I can use a stream for this?
quoteOrders is a list of order objects that implement IOrder. If the data is something like bookOrder the if statement will be true for that object and then return bookOrder.handleOrderRequest(data).
For(IOrder order: quoteOrders){
if(order.hasRequest(data)){
return order.handleOrderRequest(data);
}
}
I thought about maybe changing it to something like but I am not sure how to add the return statement part or if it is even possible with streams.
return quoteOrders.forEach(quoteOrder -> { quoteOrders.stream().filter(order -> order.hasRequest(data))
You could use filter to emulate the if statement, and then return the findFirst of the stream:
return quoteOrders.stream()
.filter(order -> order.hasRequest(data))
.findFirst()
.map(order -> order.handleOrderRequest(data))
.orElse(null); // or some other default behavior

How to avoid nested forEach calls?

I have the following code:
interface Device {
// ...
boolean isDisconnected();
void reconnect();
}
interface Gateway {
// ...
List<Device> getDevices();
}
...
for (Gateway gateway : gateways) {
for(Device device : gateway.getDevices()){
if(device.isDisconnected()){
device.reconnect();
}
}
}
I want to refactor the code using Stream API. My first attempt was like the following:
gateways
.stream()
.forEach(
gateway -> {
gateway
.getDevices()
.parallelStream()
.filter(device -> device.isDisconnected())
.forEach(device -> device.reconnect())
;
}
)
;
I didn't like it so after some modifications I ended up with this code:
gateways
.parallelStream()
.map(gateway -> gateway.getDevices().parallelStream())
.map(stream -> stream.filter(device -> device.isDisconnected()))
.forEach(stream -> stream.forEach(device -> device.reconnect()))
;
My question is whether there is a way to avoid nested forEach.
You should flatten the stream of streams using flatMap instead of map:
gateways
.parallelStream()
.flatMap(gateway -> gateway.getDevices().parallelStream())
.filter(device -> device.isDisconnected())
.forEach(device -> device.reconnect());
I would improve it further by using method references instead of lambda expressions:
gateways
.parallelStream()
.map(Gateway::getDevices)
.flatMap(List::stream)
.filter(Device::isDisconnected)
.forEach(Device::reconnect);
Don't refactor your code into using Streams. You gain no benefits and gain no advantages over doing it like this, since the code is now less readable and less idiomatic for future maintainers.
By not using streams, you avoid nested forEach statements.
Remember: streams are meant to be side-effect free for safer parallelization. forEach by definition introduces side-effects. You lose the benefit of streams and lose readability at the same time, making this less desirable to do at all.
I would try this with a sequential stream before using a parallel one:
gateways
.stream()
.flatMap(gateway -> gateway.getDevices().stream())
.filter(device -> device.isDisconnected())
.forEach(device -> device.reconnect())
;
The idea is to create a stream via gateways.stream() then flatten the sequences returned from gateway.getDevices() via flatMap.
Then we apply a filter operation which acts like the if statement in your code and finally, a forEach terminal operation enabling us to invoke reconnect on each and every device passing the filter operation.
see Should I always use a parallel stream when possible?

RxJava Having trouble Converting one list to another list

I am trying to convert Flowable<List<TaskEntity>> to Flowable<List<Task>> but something is wrong.
To understand the problem I tried with converting a simpler list and it is working fine. When I try to apply the same logic to my actual problem, it is not working.
This logic is giving me expected output. [No.1 No.2 No.3]
Flowable.fromArray(Arrays.asList(1,2,3))
.flatMapIterable(ids->ids)
.map(s->"No. "+s)
.toList()
.toFlowable()
.subscribe(
t -> Log.d(TAG, "getAllActiveTasks: "+t)
);
This logic is not working . It prints Nothing
mTaskDao.getAllTasks(STATE_ACTIVE)
.flatMapIterable(task -> task)
.map(Task::create)
.toList()
.toFlowable()
.subscribe(
t -> Log.d(TAG, "getAllActiveTasks: "+t)
);
Edit 1
This is how Task.create() looks like.
public static Task create(TaskEntity eTask) {
Task task = new Task(eTask.getTaskId(), eTask.getTaskTitle(), eTask.getTaskStatus());
task.mTaskDescription = eTask.getTaskDescription();
task.mCreatedAt = eTask.getCreatedAt();
task.mTaskDeadline = eTask.getTaskDeadline();
return task;
}
Solution
As mentioned in the comments, toList() can only work if emitting source has finite number of items to emit. Since Flowable from Dao method contains an infinite stream of objects, toList() was not being used correctly by me.
Checkout this comment for the exact way to solve this problem.
https://stackoverflow.com/a/50318832/4989435
toList requires a finite source but getAllTasks is likely infinite, which is unfortunately quite typical from DAOs backed by Android databases. Change the getAllTasks to Single, use take(1), use timeout(), or use flatMap(Observable.fromIterable().map().toList()) instead of flatMapIterable.
I want to receive any updates made to tasks in db.
In this case, you need the latter option:
mTaskDao.getAllTasks(STATE_ACTIVE)
.flatMapSingle(task ->
Observable.fromIterable(task)
.map(Task::create)
.toList()
)
.subscribe(
t -> Log.d(TAG, "getAllActiveTasks: "+t)
);
You should use only map operator to convert TaskEntity to Task. I have created sample. You can check my solution which uses only map operator

How to create a list of instances efficiently in java using a another list of instances

I have a list of instances where I need to create another list of instances using the instances in the first list.
As the example shown below, I can use a foreach or a for loop. Is there a better way to do this more efficiently?
List<Mesage> messages;
List<ArchMessage> archMessages = new ArrayList<>();
for(Message message : messages) {
archMessages.add(new ArchMessage(message));
}
You can use Java 8 Streams:
List<ArchMessage> archMessages =
messages.stream()
.map(message -> new ArchMessage(message))
.collect(Collectors.toList());
or
List<ArchMessage> archMessages =
messages.stream()
.map(ArchMessage::new)
.collect(Collectors.toList());
If you're unable to use streams from Java 8, Apache Commons Collections library provides the following:
http://commons.apache.org/proper/commons-collections/
Collection<ArchMessage> archMessages = CollectionUtils.collect(messages, new Transformer() {
public Object transform(Object o) {
return new ArchMessage((Mesage) o);
}
});
That's as efficient as it gets but let's say your code could benefit from parallelism then using the streams API would be ideal as we can get parallelism for free
by using parallelStream e.g.
List<ArchMessage> resultSet = messages.parallelStream()
.map(ArchMessage::new)
.collect(Collectors.toList());
Other than that your current approach seems good and readable.
Feedback to your recherche:
I would recommend to don't use a foreach. It will throw your efficient down cause of an inner request at each. I would recommend to use a for each time..

Categories