I have a list of Observables (RxJava 1).
List<Observable> observableList = new ArrayList<>();
It can contain at least 1 Observable. Each has the same type of the result.
How can I zip results of the all Observables?
I thought about zip-operator but it doesn't support List and I don't know quantity of observables (it can be 1,2,3,4....)
You can use the static zip(java.lang.Iterable<? extends Observable<?>> ws,FuncN<? extends R> zipFunction) method.
It is a zip method that takes an Iterable of Observables and a FuncN (which takes a varargs parameter for its call method) and uses it to combine the corresponding emitted Objects into the result to be omitted by the new returned Observable.
So for example you could do:
Observable.zip(observableList, new FuncN(){
public ReturnType call(java.lang.Object... args){
ReturnType result; //to be made
//preparatory code for using the args
for (Object obj : args){
ReturnType retObj = (ReturnType)obj;
//code to use the arg once at a time to combine N of them into one.
}
return result;
}
});
ReactiveX - Zip operator
Zip beyond BiFunction
Zip combine the emissions of multiple Observables together via a
specified function and emit single items for each combination based on
the results of this function
Here, list is an Array List of Observables of whichever type you want to pass.
val list = arrayListOf<Observable<ImageUrlResponse>>()
Observable.zip(list) { args -> Arrays.asList(args) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
val response = it[0]
val imageUrlResponse = imageUrlObject as ImageUrlResponse
urls.add(imageUrlResponse.imageUrl)}
}, {
val c = it
})
The Result of the following subscription is this image below. Just like we expect it to be zipped together. Also can you notice it returns all the responses to be zipped in a single java.lang.Object[].
Note I had to type cast my Array List to access my single object because it is of type Any!
I struggled with this as well, and used Sharan's solution as a base for mine.
My use case was doing API calls to several 3rd party providers, and then putting each individual result in a List. Each item in the list contains what the API returned, be it success or failure.
In the end it actually looks quite elegant. In my specific case "ResultType" was replaced with something like "ApiGenericResponseObject".
Observable.zip(listOfObservables, args -> {
List<ResultType> result = new ArrayList<>();
for (Object o: args) {
ResultType c = (ResultType) o;
// additional code here, I am just concatenating them together
// This gives me a list with the individual result of each Observable (for instance an API call)
result.add(c);
}
return result;
});
Alternatively, as a Lambda it looks neater. Though I wonder whether someone reading this will understand what is going on:
Observable.zip(listOfObservables, args -> Arrays.stream(args)
.map(object -> (ResultType) object)
.collect(Collectors.toList())
);
Hope it helps!
Related
I'm new to Java 8 Streams and I'm currently trying to convert a for loop into a java 8 stream. Could I get some help?
for (Subscription sub : sellerSubscriptions) {
if (orders.get(Product).test(sub)) {
orderableSubscriptions.add(sub.getId());
}
}
sellerSubscriptions = List.
orders = Map<String,Predicate<Subscription>>
orderableSubscriptions = Set<String>
Create a Stream of Subscriptions via the Collection#stream() method
Use of the Stream#filter() method to "simulate" the if statement, by filtering out all subscription that don't pass the given predicate.
By using the Stream#map() method you convert your stream of subscriptions, to a stream of ids
Finally by using the Stream#collect() you can collect the stream into anything you'd like. E.g. a Set
Your code could look like this:
Set<String> ids = sellerSubscriptions.stream() // create a Stream<Subscription>
.filter(orders.get(Product)::test) // filter out everthing that doesn't match
.map(Subscription::getId) // only use the ids from now on
.collect(Collectors.toSet()); // create a new Set from the elements
Some notes:
Subscription::getId (a method reference) is functionally equal to the lambda sub -> sub.getId()
orders.get(Product)::test (also a method reference) retrieves the predicate only once. As it seems to be the same predicate for all your subscriptions
Though it is not equal to sub -> orders.get(Product).test(sub) as that would invoke orders.get(Product) for every element
I have a class which is of the following definition
public class MyClass {
int val;
type t;
}
Where type is an enum with values A,B,C,D,....
I have a list of objects of MyClass and I want to filter out the first element of each type occurring in the list.
for example :-
Given list:
{{1,A},{2,A},{4,B},{5,B},{3,C}}
Output:
{{1,A},{4,B},{3,C}}
Is there a way to use filter() of a stream of the list to solve this problem?
I'm not sure if there's a way to do this with a single Stream pipeline, but you can do it with two.
The first pipeline groups the objects by the val property (producing a Map<Integer,List<MyClass>>) and the second takes the first object of each List produced by the first pipeline and collects them into the output List:
List<MyClass>
filtered = mycl.stream ()
.collect (Collectors.groupingBy (c -> c.val))
.values ()
.stream ()
.map (l -> l.get (0))
.collect (Collectors.toList ());
Here is a solution which is not as elegant I hoped for but it works:
Set<MyType> typeSet = new HashSet<>();
List<MyClass> result = list.stream()
.filter(c -> typeSet.add(c.getType())).collect(
Collectors.toList());
I'm not sure if there is any direct way of doing it but you can achieve it by doing
1) First use streams's findFirst method with filter (TypeOf type).
2) do above steps for all types.
3) Merge all above data into one list.
One of good way to achieve this override equals() and hashCode() in your MyClass class. Check equality on the basis of 'type'. Then put your List in Set it will remove all duplicate. :)
I have a loop which update an String object:
String result = "";
for (SomeObject obj: someObjectList) {
result = someMetohd(obj, result);
}
An implementation of someMethod is irrelevant:
private String someMethod(SomeObject obj, String result) {
result = result.concat(obj.toString());
return result;
}
And I want to use Stream instead a loop. How to implement it with Stream?
#SuppressWarnings("OptionalGetWithoutIsPresent")
String result = Stream.concat(Stream.of(""), someObjectList.stream())
.reduce(this::someMethod)
.get();
Your someMethod should be associative as specified in the documentation, however this is only important for parallel streams, while your code is explicitly sequential
As you always add to the result, you can consider it a first element of the stream and then use reduce method which will always merge first two elements - current result and next element
result has to be the first parameter of your someMethod
Because all elements in the stream have to be of the same type, while you have String result and SomeObject elements, you need to change the signature of someMethod to accept two Objects (and do the casts inside the method): private String someMethod(Object result, Object obj). This is the most ugly part of this solution.
You can inline the initial value of the result - no need to define result upfront
You might want to change this::someMethod depending on where this method is declared
Finally, you don't need to worry about handling Optional result, because the stream always has at least one element so it's safe to just call get()
final StringBuilder resultBuilder = new StringBuilder();
someObjectList.stream().map(SomeObject::toString).forEach(resultBuilder::append);
final String result = resultBuilder.toString();
To know more about Streams, you can check this page: http://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/, I think it's very helpful.
Although the functional equivalent of what you're trying to achieve here is possible with streams, it's worth reminding you that functional and iterative ways of thinking are not necessarily compatible.
Generally you think of each element on its own, and you don't have visibility over other elements, unless you're using a special function like reduce.
Here's something that does what you've asked for:
final List<Object> objectList = Arrays.asList("a", "b", "c", "d");
String concatString = objectList.stream()
.map(e -> e.toString())
.reduce((result, element) -> result.concat(e))
.get();
Map turns the entire stream into a list, but with the toString function called separately on every element. Reduce is more complex. It can be described as an accumulation. It executes a function between the result, and the current element. In this case, it takes the first element, and concatenates it to the second. It then takes the first/second concatenation, and applies the same function to the third. And so on.
Instead of dealing with lambdas, you can also pass in methods directly, to tighten up your code a bit:
String result = objectList.stream()
.map(Object::toString)
.reduce(String::concat)
.get();
Let's say I have a void method that just does transformation on an object, without returning any value, and I want to use it in a context of a stream map() function, like this:
public List<MyObject> getList(){
List<MyObject> objList = ...
return objList.stream().map(e -> transform(e, e.getUuid())).collect(Collectors.toList());
}
private void transform(MyObject obj, String value){
obj.setUuid("prefix" + value);
}
The example is made up for simplicity - the actual method is doing something else than just mucking up the UUID of an object.
Anyway, how is that possible to use a void method in a scenario like the above?
Surely, I could make the method return the transformed object, but that's besides the point and is violating the design (the method should be void).
Seems like this is a case of forced usage of java 8 stream. Instead you can achieve it with forEach.
List<MyObject> objList = ...
objList.forEach(e -> transform(e, e.getUuid()));
return objList;
In addition to Eugene's answer you could use Stream::map like this:
objList.stream()
.map(e -> {
transform(e, e.getUuid());
return e;
}).collect(Collectors.toList());
Actually, you don't want to transform your current elements and collect it into a new List.
Instead, you want to apply a method for each entry in your List.
Therefore you should use Collection::forEach and return the List.
List<MyObject> objList = ...;
objList.forEach(e -> transform(e, e.getUuid()));
return objList;
If you are sure that this is what you want to do, then use peek instead of map
You can use the peek(Consumer<? super T> action) method. It takes a consumer as a parameter, performs an action on each of the elements and returns a stream.
objList.stream().peek(e -> transform(e,e.getUuid())).collect(Collectors.toList());
To be clear I don't have any problems and don't really need help but I wanted to ask anyway:
Let's say we have a String array
String[] sarr = new String[]{"POTATO", "TOMATO"};
and we have an enum
public enum Food{POTATO, TOMATO, PIZZA}
If I wanted to check if all Strings in sarr are present in Food, I'd do the following:
ArrayList<String> foodstrings = new ArrayList<>();
Arrays.asList(Food.values()).forEach((in) -> foodstrings.add(in.toString()));
if (!foodstrings.containsAll(Arrays.asList(sarr))) doStuff();
Is there a way to do this in less lines of code? Or simply a more elegant way?
You want to determine if all element in your array are contained in the list of food names.
A possible solution is to convert the food names to a Set (to have a O(1) contains); then, we need to determine if all elements in the array are contained in this set:
public static void main(String[] args) {
String[] sarr = new String[]{"POTATO", "TOMATO"};
Set<String> set = Arrays.stream(Food.values()).map(Enum::name).collect(Collectors.toSet());
boolean result = Arrays.stream(sarr).allMatch(set::contains);
}
In your current solution, you are mutating an external variable with forEach, which is a bad practice.
I believe a better version of the first two lines would be:
Set<String> foodstrings = Arrays.stream(Food.values()).map(Enum::name).collect(Collectors.toSet());
Using Set instead of List will improve performance of containsAll, and the code is entirely streamed, instead of using forEach and an external collector.
The if is still good, although you could just combine it all into a single statement (formatted for readability):
if (! Arrays.stream(Food.values())
.map(Enum::name)
.collect(Collectors.toSet())
.containsAll(Arrays.asList(sarr))) {
doStuff();
}
if (Stream.of(sarr).allMatch(s -> Stream.of(Food.values()).anyMatch(t -> s.equals(t.name()))))
{
// all match
}
Create a stream out of sarr (which could be any Collection of objects amenable to the Stream API introduced in Java 1.8)
We ask for the value allMatch, which only returns true if a Predicate (i.e. a function that returns true/false).
For the Predicate expected by allMatch, we provide a lambda that iterates over a second set of objects via a stream, and calls anyMatch: a simple Predicate that will return true if any member object satisfies a provided condition (once again, a boolean function).
We provide yet another lambda to anyMatch which compares members of the 2 collections via their equals implementations.
This solution is semantically equivalent to the invariant
A \subset B
which in our case is
sarr \subset Food.values()
and the following Java < 1.8 code shown below with short-circuiting to mimic the specification (minus the streams overhead):
// assume success, since if both sets are empty the invariant holds
boolean subset = true;
for (String a : sarr)
{
if (null == a) continue;
boolean contained = false;
for (Food b : Food.values())
if (b.name().equals(a)) { contained = true; break; }
if (!contained) { subset = false; break; }
}
if (subset)
{
// all match
}
You could, of course, substitute different collection types and conditions, as well as use parallelStream() to make better use of the hardware available.