I have a method which returns a Flowable<RealmResults<MyClass>>. For those not familiar with Realm, RealmResults is just a simple List of items.
Given a Flowable<RealmResults<MyClass>>, I'd like to emit each MyClass item so that I can perform a map() operation on each item.
I am looking for something like the following:
getItems() // returns a Flowable<RealmResults<MyClass>>
.emitOneAtATime() // Example operator
.map(obj -> obj + "")
// etc
What operator will emit each List item sequentially?
You would flatMap(aList -> Flowable.fromIterable(aList)). Then you can map() on each individual item. There is toList() if you want to recollect the items (note: this would be a new List instance). Here's an example illustrating how you can use these methods to get the different types using List<Integer>.
List<Integer> integerList = new ArrayList<>();
Flowable<Integer> intergerListFlowable =
Flowable
.just(integerList)//emits the list
.flatMap(list -> Flowable.fromIterable(list))//emits one by one
.map(integer -> integer + 1);
The question is, do you want to keep the results as a Flowable<List<MyClass>> or as a Flowable<MyClass> with retained order?
If the first,
getItems()
.concatMap(results -> Flowable
.fromIterable(results)
.map(/* do your mapping */)
.toList()
)
If the second, this should suffice:
getItems()
.concatMap(Flowable::fromIterable)
.map(/* do your mapping */)
Related
So I'm wondering what is the best solution to the following problem:
I have a list of items (a custom class) in a java collection ex
List<Item> itemList = ... item1,item2,item3 etc
Each item in the collection however has a corresponding logical pair also in the collection (so the pair's are not necessarily following each other by index in the collection)
I have a helper method like
Item calculateCorrectItem(Item item1, Item item2)
which can return the correct one of a pair based on some business logic (the details of that is not relevant)
I would like to replace an item and its pair in the collection, with the result of the method above - so that every 2 elements of a pair in the collection are replaced with the calculated one based on those two.
Some details:
We can assume that every element has one and only one pair.
Each item has the pair's ID as a property, like
public class Item {
private String id;
private String pairId;
the equal method is true when the ID of two items are the same.
...getters,setters
}
Also, the references in the collection which i want to filter are also existing in a global cache, where every Item can be easily retrieved from, like
globalCache.getItemById(String id)
So an actual pair reference can be easily retrieved if the ID of the pair is known.
What could be an elegant solution (maybe by utilizing the Stream IPA)? In the end, the only expectation is that the collection contains one Item of each pair, the ordering doesn't matter.
With streams, you would have to do this using indexed access:
List<Item> calculated =
IntStream.range(0, itemList.size() / 2)
.mapToObj(i -> calculateCorrectItem(itemList.get(2*i+0), itemList.get(2*i+1))
.collect(toList());
If you want to merge items based on their IDs, you can group the items by their ID:
itemList.stream()
.collect(groupingBy(Item::getId)) // Yields a Map<IdType, List<Item>>
.values() // Yields a Collection<List<Item>>, where each List<Item> contains items with the same id.
.stream()
.map(is -> /* invoke calculateCorrectItem, handling 1, 2 or more items in the list */)
.collect(...)
Here's another approach that performs a mutable reduction using a map (you can use a hash map if preserving the source list's order of pair IDs is unimportant):
Collection<Item> correctItems1 = itemList.stream().collect(
LinkedHashMap<String, Item>::new,
(map, item) -> map.merge(item.getPairId(), item, this::calculateCorrectItem),
Map::putAll)
.values();
List<Item> result = new ArrayList<>(correctItems1);
I'm assuming that method calculateCorrectItem(Item item1, Item item2) will produce the same result regardless of the order of the arguments and that duplicated results has to be removed from the resulting list.
List<Item> items = ... ; // obtain the items
Map<String, Item> itemById = items.stream()
.collect(Collectors.toMap(Item::getId, // relies on uniquness of Id
Function.identity()));
// set is used to alliminate duplicates since their order is not important
Set<Item> itemSet = items.stream()
.map(item-> pairById.containsKey(item.getPairId()) ? item : // if pair isn't present return the same item, othewise merge them
calculateCorrectItem(item, pairById.get(item.getPairId())))
.collect(Collectors.toSet());
List<Item> result = new ArrayList<>(itemSet);
Here is another approach using a Collectors.toMap with a merge function:
First, create a record for demo and intialize a list with some data
record Item(String getId, int getValue) {
}
Random r = new Random();
List<Item> items = r.ints(10, 1, 5)
.mapToObj(id -> new Item(id+"", r.nextInt(100) + 1))
.toList();
System.out.println("The raw data");
items.forEach(System.out::println);
System.out.println();
Now stream the list
use the third argument of toMap to "merge" the new items.
Collection<Item> collection = items.stream()
.collect(Collectors.toMap(Item::getId, item->item,
(item1, item2) -> calculateCorrectItem(item1,
item2)))
.values();
System.out.println("The new list of combined items");
collection.forEach(System.out::println);
Prints
The raw data
Item[getId=1, getValue=14]
Item[getId=4, getValue=42]
Item[getId=4, getValue=19]
Item[getId=2, getValue=16]
Item[getId=4, getValue=20]
Item[getId=3, getValue=57]
Item[getId=3, getValue=47]
Item[getId=3, getValue=22]
Item[getId=1, getValue=3]
Item[getId=4, getValue=73]
The new list of combined items
Item[getId=1, getValue=17]
Item[getId=2, getValue=16]
Item[getId=3, getValue=126]
Item[getId=4, getValue=154]
The method used for the above. It just sums the values and returns a new Item instance.
public static Item calculateCorrectItem(Item one, Item two) {
return new Item(one.getId(), one.getValue() + two.getValue());
}
And a simple non-stream solution prints out the same results as before.
Map<String, Item> result = new HashMap<>();
for (Item item : items) {
result.compute(item.getId(),
(k, v) -> v == null ? item : new Item(v.getId(),
v.getValue() + item.getValue()));
}
result.values().forEach(System.out::println);
Please can you help me resolve this issue I am having when attempting to stream through an array list and call a setter based on a method which returns a Boolean.
Written as a for loop, it would look like this:-
for (final PersonDto person : personList) {
person.setUserCanEdit(userHasWriteRole(person));
}
private Boolean userHasWriteRole(final PersonDto person) {
return getUserRoles().contains(getReadRole());
}
I have tried a few variations with no success, along the following lines
final List<PersonDto> results = personList.stream().filter(a -> a.setUserCanEdit(this::userHasWriteRole)).collect(Collectors.toList());
... But it complains with
The target type of this expression must be a functional interface
I think I would go for:
personList.stream()
.filter(p -> userHasWriteRole(p))
.forEach(p -> p.setUserCanEdit(true));
I think this keeps the intent clear,
Since you are updating the objects in array list, you can use forEach
personList.forEach(person ->person.setUserCanEdit(userHasWriteRole(person)));
The filter() method is an intermediate operation of the Stream interface that allows us to filter elements of a stream that match a given Predicate. You can't update data in the filter.
Use forEach for this.
personList.stream().forEach(a -> a.setUserCanEdit(userHasWriteRole(a)));
And if you want to get in new arraylist make a copy of list and do this operations on new list
List<PersonDto> copy = new ArrayList<>(personList);
copy.stream().forEach(a -> a.setUserCanEdit(userHasWriteRole(a)));
If you want to have a list with filtered elements and do the setUserCanEdit on every elements in that list; you can do like this:
List<PersonDto> newList = personList.stream()
.filter(p -> userHasWriteRole(p))
.collect(Collectors.toList());
newList.forEach(p -> p.setUserCanEdit(true));
I have huge list with logging objects and I need to take only first n entries based on first object field value (list.get(0).getId())
I do this way:
List<Event> list = asList(
new Event("id_1", "value_1" ...),
new Event("id_1", "value_2" ...),
new Event("id_1", "value_3" ...),
new Event("id_2", "value_4" ...),
new Event("id_2", "value_5" ...),
new Event("id_2", "value_6" ...));
String id = events.get(0).getId();
return list.stream()
.filter(event -> event.getId().equals(id))
.collect(toList());
And it works, but it looks like not true way. Can you give me an advice how to do this better?
If you want to avoid the initial access to the list (to obtain the Id of the first event), you can group the events by Id, and return the first group of events:
return list.stream()
.collect(Collectors.groupingBy(Event::getId,
LinkedHashMap::new,
Collectors.toList()))
.values()
.iterator()
.next();
P.S. if the list can be empty, you still have to check that .values().iterator().hasNext().
In java 8, collect emp object based on some filter condition.
In main class:
List<Emp> empList = Arrays.asList(
new Emp("aaa", language1),
new Emp("cc", language2),
new Emp("bb", language3),
new Emp("dd", language3)
);
empList.stream()
.flatMap(s->s.getLanguage().stream())
.filter(s->s.equals("java"))
.forEach(System.out::println); //Here just i am printing.
Actually I need to collect new List<EMP>.
How to collect emp object that are all have language "java". How can I do this?
You should not use flatMap if you want to collect Emp objects in the end because it will change every element to something else and it can be quite hard to map them back.
You should put all your logic in a filter: "keep the Emp object if getLanguage contains "java"".
empList.stream()
.filter(x->x.getLanguage().contains("java"))
.collect(Collectors.toList());
You can also do like this:
List<Object> optionMetas = new ArrayList<>();
Map<Long, Object> optionIdMetaMap_ = optionMetas.stream()
.filter(option -> option.getXX() || option.getXXX().equal("java"))
.collect(Collectors.toMap(Object::getKEY, item -> item));
Add your relevant condition in filter().
You can process the current list using removeIf method that accepts a filter predicate:
empList.removeIf(e -> !e.getLanguage().equals("java"));
Or you can copy the current list to another list and do the same.
I have a method: Observable<List<String>> getNames();
I have object Person with constructors Person() and Person(String name)
Also there is a empty list ArrayList<Person> cachedPersons.
What I need:
In my method using RxJava fill array with Person using constructor<String> from List<String> like this:
ArrayList<String> cachedPersons = new ArrayList<Person>();
Observable<List<String>> getNames(){
Observable.from(getNames())
.map{
//cachedPersons.addAll(/*add new Person with corresponding String*/)
//with List("John","Sara","David")
//create cachedPersons = List(Person1("John"),Person2("Sara"),Person3("David"))
}
}
Observable.from(getNames())
.map{ name -> new Person(name)}
.toList()
.subscribe(
list -> cachedPersons.addAll(list)
);
The problem is that you are trying to fill needed ArrayList from map() method, and it is wrong.
It will never be executed until you make a subscription. You are not doing it in your code, so your ArrayList will not be filled with above code.
Do subscription and in subscription onNext you can fill your ArrayList.
Maybe this
Observable.from(getNames())
.doOnNext(name -> cachedPersons.add(new Person(name)))
.toList()
.subscribe();
This way you are just filling it without changing the stream value.
Observable<SourceObjet> source = ...// get first list from here
source.flatMapIterable(list -> list)
.map(item -> new ResultsObject().convertFromSource(item))
.toList()
.subscribe(transformedList -> ...);
If your Observable emits a List, you can use these operators:
flatMapIterable -> transform your list to an Observable of items
map -> transform your item to another item
toList -> transform a completed Observable to a Observable which emit a list of items from the completed Observable