I have a Map of Lists, and would like to collect all the values and then stream on the collected List, so given:-
Map<LocalDateTime, List<PublicationSession>> effectiveDatePublicationMap;
Below works / shows what I want to do with iteration but I want to do this all in streams, rather than creating an intermediate list using iteration:-
List< PublicationSession> publicationsToFilter = new ArrayList<>();
for (List< PublicationSession> sessions : effectiveDatePublicationMap.values()) {
publicationsToFilter.addAll(sessions);
}
Collection< PublicationSession > wantedPublications = publicationsToFilter.stream()
.filter(pub -> PublicationStatus.valueOf(pub.getPublishStatus()) == PublicationStatus.COMPLETE)
.sorted(Comparator.comparing(PublicationSession::getCreateTime))
.collect(toMap(p -> p.getPublicationSession().getEffDateTime(), UnaryOperator.identity(), PICK_LATEST))
.values();
So I wanted to stream the map of lists all into one lists, then work on that sublist in one stream rather than having to do the above in two statements (ie build up the list by iteration, then do the streaming to filter / sort and collect.
I have so far failed to do so, when I try and stream / collect the map I get compile issues. Could anyone show how to do the above in one step, without having to do iteration then streaming?
It looks like all you need is flatMap the values of the original Map and then continue processing as you currently do:
Collection< PublicationSession > wantedPublications =
effectiveDatePublicationMap.values() // Collection<List<PublicationSession>>
.stream() // Stream<List<PublicationSession>>
.flatMap(list->list.stream()) // Stream<PublicationSession>
.filter(pub -> PublicationStatus.valueOf(pub.getPublishStatus()) == PublicationStatus.COMPLETE)
.sorted(Comparator.comparing(PublicationSession::getCreateTime))
.collect(toMap(p -> p.getPublicationSession().getEffDateTime(), UnaryOperator.identity(), PICK_LATEST))
.values();
To obtain the same result of your iterative code
List< PublicationSession> publicationsToFilter = new ArrayList<>();
for (List< PublicationSession> sessions : effectiveDatePublicationMap.values()) {
publicationsToFilter.addAll(sessions);
}
with streams you first have to use flatMap to transoform your stream of List<PublicationSession> in a stream of PublicationSession, and then collect all of them in a list with collect(Collectors.toList())
List<PublicationSession> publicationsToFilter = effectiveDatePublicationMap.values()
.flatMap(Collection::stream)
.collect(Collectors.toList());
Before collecting the result, you can filter or sort as you wish.
Related
How can I achieve the same logic in my code using only Streams, without the for loop as shown in my code below?
I have tried using flatMap, but I get stuck on the condition part, since allMatch() only returns a boolean.
How can I retrieve all the rows from the nested ArrayList that passes the condition without using for loop?
ArrayList<ArrayList<Tile>> completeRows = new ArrayList<>();
for (ArrayList<Tile> rows: getGridTiles()) {
if (rows.stream().allMatch(p -> p.getOccupiedBlockType() != BlockType.EMPTY)) {
completeRows.add(rows);
}
}
You can apply filter() with a nested stream (exactly the same as you've used as a condition in your code) passed to it as a Predicate to verify that a list consists of only non-empty tiles.
And then collect all the lists (rows) that have passed the predicate into a List using collect().
public static List<List<Tile>> getNonEmptyRows(List<List<Tile>> rows) {
return rows.stream()
.filter(row -> row.stream().allMatch(tile -> tile.getOccupiedBlockType() != BlockType.EMPTY))
.collect(Collectors.toList()); // or .toList() with Java 16+
}
I have tried using flatMap
You need to use flatMap when your goal is to flatten the steam of collections (or objects holding a reference to a collection) to a stream of elements of these collections. In these case, turn a stream of lists of tiles Stream<List<Tile>> into a stream of tiles Stream<Tile>.
Judging by your code, it's not you what you want because you're accumulating the rows (lists of tiles) into another list and not "flattening" them.
But just in case, that's how it can be done:
public static List<Tile> getNonEmptyTiles(List<List<Tile>> rows) {
return rows.stream()
.filter(row -> row.stream().allMatch(tile -> tile.getOccupiedBlockType() != BlockType.EMPTY))
.flatMap(List::stream)
.collect(Collectors.toList()); // or .toList() with Java 16+
}
Sidenote: leverage abstract data types - write your code against interfaces. What does it mean to "program to an interface"?
The following lambda expressions take the values of a HashMap, gets a List of Objects in its ArrayList, adds all such Objects to another ArrayList, and then prints each attribute if a condition is met.
This works, but I am a bit frustrated I couldn't figure out how to do this in one step, as in, not using two lambda expressions.
Map<Integer, Person> people = new HashMap<Integer, Person>();
...
List<Object> objects = new ArrayList<Object>();
people.values().stream()
.map(p->p.getObjects())
.forEach(p->objects.addAll(p)); //note: can be multiple
objects.stream()
.filter(p->p.getClass().toString().contains("Keyword"))
.forEach(p->System.out.println(p.display()));
So is there a way I can go from line 2 to line 5 directly, which would in effect convert a stream of List of Objects to a stream of all of the Objects themselves?
You could merge your operations to a single stream pipeline as
List<Pet> cats = people.values().stream()
.flatMap(p -> p.getPets().stream())
.filter(p -> p.getClass().toString().contains("Cat")) // or Cat.class::isInstance
.collect(Collectors.toList());
and then perform operations on them as in your code such as
cats.forEach(cat -> System.out.println(cat.getName()));
An overall transformation of your code would look like:
Map<Integer, Person> people = ...;
people.values().stream()
.flatMap(p -> p.getPets().stream())
.filter(p -> p.getClass().toString().contains("Cat"))
.forEach(cat -> System.out.println(cat.getName()));
I have a stream of orders (the source being a list of orders).
Each order has a Customer, and a list of OrderLine.
What I'm trying to achieve is to have a map with the customer as the key, and all order lines belonging to that customer, in a simple list, as value.
What I managed right now returns me a Map<Customer>, List<Set<OrderLine>>>, by doing the following:
orders
.collect(
Collectors.groupingBy(
Order::getCustomer,
Collectors.mapping(Order::getOrderLines, Collectors.toList())
)
);
I'm either looking to get a Map<Customer, List<OrderLine>>directly from the orders stream, or by somehow flattening the list from a stream of the Map<Customer>, List<Set<OrderLine>>> that I got above.
You can simply use Collectors.toMap.
Something like
orders
.stream()
.collect(Collectors
.toMap(Order::getCustomer
, Order::getOrderLines
, (v1, v2) -> { List<OrderLine> temp = new ArrayList<>(v1);
temp.addAll(v2);
return temp;});
The third argument to the toMap function is the merge function. If you don't explicitly provide that and it there is a duplicate key then it will throw the error while finishing the operation.
Another option would be to use a simple forEach call:
Map<Customer, List<OrderLine>> map = new HashMap<>();
orders.forEach(
o -> map.computeIfAbsent(
o.getCustomer(),
c -> new ArrayList<OrderLine>()
).addAll(o.getOrderLines())
);
You can then continue to use streams on the result with map.entrySet().stream().
For a groupingBy approach, try Flat-Mapping Collector for property of a Class using groupingBy
I have a Map<Integer, List<MyDayObject>> that represents all days by week of year.
Now I need to analyze only the days that produced the highest value for each week. I have been able to get the nested streaming working with a forEach but I can't seem to be able to correctly stream it and collect into a List at the end.
weeks.forEach((k, v) -> {
MyDayObject highest = v.stream()
.max((o1, o2) -> o1.value().compareTo(o2.value())).get();
System.out.println(highest);
});
What I need though is not to print it but get a List<MyDayObject> allHighest at the end.
I tried .filtering the .entrySet() by .max() but can't get that to work.
What's the best way to filter a Map by max of the nested List?
It appears you need to map each value of the original Map to the corresponding max MyDayObject:
List<MyDayObject> allHighest =
weeks.values() // produces a Collection<List<MyDayObject>>
.stream() // produces a Stream<List<MyDayObject>>
.map(list -> list.stream() // transforms each List<MyDayObject> to a MyDayObject
// to obtain a Stream<MyDayObject>
.max((o1, o2) -> o1.value().compareTo(o2.value())).get())
.collect(Collectors.toList()); // collects the elements to a List
P.S. I just copied your code that finds the maximum element of a given list without trying to improve it, which may be possible. I focused on getting the desired output.
As holi-java commented, the lambda expression that compares the elements can be simplified:
List<MyDayObject> allHighest =
weeks.values()
.stream()
.map(list -> list.stream().max(Comparator.comparing(MyDayObject::value)).get())
.collect(Collectors.toList());
Map is Map<String, List<User>> and List is List<User>. I want to use
Map<String,List<User>> newMap = oldMap.stream()
.filter(u ->userList.stream()
.filter(ul ->ul.getName().equalsIgnoreCase(u.getKey()).count()>0))
.collect(Collectors.toMap(u.getKey, u.getVaule()));
can't change to new Map. Why?
There are several problems with your code:
Map does not have a stream(): its entry set does, so you need to call entrySet() first.
There are a couple of misplaced parentheses
Collectors.toMap code is incorrect: you need to use the lambda u -> u.getKey() (or the method-reference Map.Entry::getKey) and not just the expression u.getKey(). Also, you mispelled getValue().
This would be a corrected code:
Map<String, List<User>> newMap =
oldMap.entrySet()
.stream()
.filter(u -> userList.stream()
.filter(ul ->ul.getName().equalsIgnoreCase(u.getKey())).count() > 0
).collect(Collectors.toMap(u -> u.getKey(), u -> u.getValue()));
But a couple of notes here:
You are filtering only to see if the count is greater than 0: instead you could just use anyMatch(predicate). This is a short-cuiting terminal operation that returns true if the predicate is true for at least one of the elements in the Stream. This has also the advantage that this operation might not process all the elements in the Stream (when filtering does)
It is inefficient since you are traversing the userList every time you need to filter a Stream element. It would be better to use a Set which has O(1) lookup (so first you would convert your userList into a userSet, transforming the username in lower-case, and then you would search this set for a lower-case value username).
This would be a more performant code:
Set<String> userNameSet = userList.stream().map(u -> u.getName().toLowerCase(Locale.ROOT)).collect(toSet());
Map<String,List<User>> newMap =
oldMap.entrySet()
.stream()
.filter(u -> userNameSet.contains(u.getKey().toLowerCase(Locale.ROOT)))
.collect(Collectors.toMap(u -> u.getKey(), u -> u.getValue()));
Perhaps you intended to create a Stream of the entry Set of the input Map.
Map<String,List<User>> newMap =
oldMap.entrySet().stream()
.filter(u ->userList.stream().filter(ul ->ul.getName().equalsIgnoreCase(u.getKey())).count()>0)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
This would create a Map that retains the entries of the original Map whose keys equal the name of at least one of the members of userList (ignoring case).