Is there flatMap analogy in Cactoos library? I need exactly what flatMap can, but without streams:
The flatMap() operation has the effect of applying a one-to-many transformation to the elements of the stream, and then flattening the resulting elements into a new stream.
E.g. if I have some values in list, and each value has children items, and I want to get all items from each value, I can use flatMap:
List<Value> values = someValues();
List<Item> items = values.stream()
.flatMap(val -> val.items().stream()) // val.items() returns List<Item>
.collect(Collectors.toList());
How to do the same thing using Cactoos instead of streams API?
You can use Joined, it is the equivalent of flattening an Iterable.
For example, you would write:
new Joined<>(new Mapped<>(val -> val.items(), someValues()));
Related
I'm trying to convert a list of objects into a list of lists of objects, i.e. List<T> into List<List<T>> and group them by a list of strings used as sorting keys.
The problem is that after the groupingBy(), the return value is a map, and that map a different order of values.
What can I do to preserve the initial order of elements in the list groupedTreesList?
My code:
Map<List<String>,List<QueryTreeProxy>> groupedMap = treesProxyList.stream()
.collect(Collectors.groupingBy(
QueryTreeProxy::getMappingKeys,
Collectors.toList()
));
List<List<QueryTreeProxy>> groupedTreesList = groupedMap.values().stream().toList();
groupingBy(), the return value is a map and that map output a different order of values list
You can use another flavor of groupingBy(classifier, mapFactory, downstream), which allows you to specify the type of the Map. And we can use LinkedHashMap to preserve the initial order.
And there's no need to generate the second stream, you can use parameterized contractor of ArrayList instead.
Map<List<String>, List<QueryTreeProxy>> groupedMap = treesProxyList.stream()
.collect(Collectors.groupingBy(
QueryTreeProxy::getMappingKeys,
LinkedHashMap::new,
Collectors.toList()
));
List<List<QueryTreeProxy>> groupedTreesList = new ArrayList<>(groupedMap.values());
Besides that, using a mutable object as a key isn't a good practice.
I have a method that receives a collector. The collector should combine incoming lists:
reducing(Lists.newArrayList(), (container, list) -> {
container.addAll(list);
return container;
})
This seems like a very common scenario to me, and I believe Java's own collector should have something to cover this use case, but I cannot remember what. Besides, I want it to return ImmutableList.
To obtain a "flattened" list of elements from a nested collection firstly you need to apply flatMapping() which takes a function that turns an element to a stream and a collector as second argument. And to get an immutable list apply toUnmodifiableList() as a downstream collector inside the flatMapping() (as already mentioned by racraman and hfontanez in their comments, since they pointed that earlier the credits for this answer should belong to them).
List<List<Integer>> nestedList = List.of(
List.of(1, 2),
List.of(3, 4)
);
nestedList.stream()
.collect(Collectors.flatMapping(List::stream,
Collectors.toUnmodifiableList()))
.forEach(System.out::println);
output
1
2
3
4
A more common use-case for Collector.flatMapping() is when each element of the stream holds a reference to a collection, rather than being a collection itself.
Consider the following scenario.
We have a collection of Orders. Each Order has an id and a collection of Items. And we want to obtain a map from this collection of orders so that based on the order id we can get a list of items.
In this situation Collector.flatMapping() is indispensable because by applying the flatMap() in the middle of the stream we will lose access to the orderId in the collect(). Hence, flattening should happen inside the collect() operation.
A method that implements the logic described above might look this:
public Map<String, List<Item>> getItemsByOrderId(Collection<Order> orders) {
return orders.stream()
// some logic here
.collect(Collectors.groupingBy(Order::getId,
Collectors.flatMapping(order ->
order.getItems().stream().filter(somePredicate),
Collectors.toList())));
}
We can also do flatMap() operation and toList() collector, to collect multiple incoming lists to one resultant list.
List<String> joinedList = Stream.of(
asList("1", "2"),
asList("3", "4")).flatMap(list -> list.stream()
)
.collect(toList());
System.out.println("Printing the list named as joinedList: " + joinedList);
Output:
Printing the list named joinedList: [1, 2, 3, 4]
I have two lists streams one of strings (counties) and one of objects(txcArray). I need to iterate through both lists and compare an instance of counties with an instance of txcArray and they match increment a counter and if they don't I would move on. I need to do this using java 8 lambda expressions and this is what I have so far.
counties.stream().forEach(a-> {
txcArray.stream()
.filter(b->b.getCounty().equals(a))
.map(Map<String,Integer>)
});
Your mistake is using forEach.
List<Long> counts = counties.stream()
.map(a -> txcArray.stream().filter(b -> b.getCounty().equals(a)).count())
.collect(Collectors.toList());
However, this is not very efficient, performing counties.size() × txcArray.size() operations. This can get out of hands easily, when the lists are larger.
It’s better to use
Map<String, Long> map = txcArray.stream()
.collect(Collectors.groupingBy(b -> b.getCounty(), Collectors.counting()));
List<Long> counts = counties.stream()
.map(a -> map.getOrDefault(a, 0L))
.collect(Collectors.toList());
This will perform counties.size() + txcArray.size() operations, which will be more efficient for larger lists, therefore, preferable, even if it’s not a single stream operation but using an intermediate storage.
I have a list containing Persons, each person has a list with his subjects inside.
I need to return a Set containing every subject using lambda, so far i've tried this:
list.stream().map(person -> person.getSubjects());
But that would get me a List> so i can't use it.
How could i print/get every string in the list of every person using lambdas?
Thanks.
list.stream().map(person -> person.getSubjects().stream()); is not a List, it's a Stream. If you want a Set, do this :
list.stream().flatMap(person -> person.getSubjects().stream()).collect(Collectors.toSet());
This will create an HashSet<Subject>. Note the use of flatMap instead of map to flatten the lists of subjects into a single stream. If you want another implementation of Set, for example TreeSet, do the following :
list.stream().flatMap(person -> person.getSubjects().stream())
.collect(Collectors.toCollection(TreeSet::new));
You can use flatMap:
Set<Subject> subjects = list.stream()
.map(person -> person.getSubjects())
.flatMap(subjects -> subjects.stream())
.collect(Collectors.toSet());
flatMap is good for "flattening" nested collections.
So I have a Stream<Collection<Long>> that I obtain by doing a series of transformations on another stream.
What I need to do is collect the Stream<Collection<Long>> into one Collection<Long>.
I could collect them all into a list like this:
<Stream<Collection<Long>> streamOfCollections = /* get the stream */;
List<Collection<Long>> listOfCollections = streamOfCollections.collect(Collectors.toList());
And then I could iterate through that list of collections to combine them into one.
However, I imagine there must be a simple way to combine the stream of collections into one Collection<Long> using a .map() or .collect(). I just can't think of how to do it. Any ideas?
This functionality can be achieved with a call to the flatMap method on the stream, which takes a Function that maps the Stream item to another Stream on which you can collect.
Here, the flatMap method converts the Stream<Collection<Long>> to a Stream<Long>, and collect collects them into a Collection<Long>.
Collection<Long> longs = streamOfCollections
.flatMap( coll -> coll.stream())
.collect(Collectors.toList());
You could do this by using collect and providing a supplier (the ArrayList::new part):
Collection<Long> longs = streamOfCollections.collect(
ArrayList::new,
ArrayList::addAll,
ArrayList::addAll
);
You don't need to specify classes when not needed.
A better solution is:
Collection<Long> longs = streamOfCollections.collect(
ArrayList::new,
Collection::addAll,
Collection::addAll
);
Say, you don't need an ArrayList but need a HashSet, then you also need to edit only one line.