Reputation: 73
I am trying to generate a SortedMap
using streams directly from two ArrayList
s that come from a two different sources. My goal is to have the SortedMap
store a Double
attribute from the second list as key and the object from the first list as the value, if those objects have a matching attribute, which is checked by a helper Object.
So far, I can get it accomplished using:
SortedMap<Double, FirstObject> myMap = new TreeMap<>(Double::compareTo);
List<FirstObject> myList = firstDao.get(someId).stream()
.filter(firstobject -> secondDao.get(firstObject.getObjectId())
.stream()
.anyMatch(secondObject -> {
if (Helper.check(secondObject).matches()) {
myMap.put(
secondObject.getEfficiency(), firstObject
);
}
return Helper.check(secondObject).matches();
}))
.collect(Collectors.toList());
I have no use for myList
that I am generating with that code, but so far it's the only way I have been able to populate the Map
.
Is there a way I can populate directly to the SortedMap
without having to generate that list?
Upvotes: 2
Views: 90
Reputation: 29028
Instead of creating a filter
that causes side-effects you need to use collect
.
One of the ways to do that is to create an intermediate Map which would associate each object returned by firstDao
with the matching pair returned by secondDao
.
Then create a stream over the entries of the intermediate map. Filter out the entry with an empty key (with no matching pair). And then apply collect(Collectors.toMap())
.
NavigableMap<Double, FirstObject> myMap = firstDao.get(tableId).stream()
.collect(Collectors.toMap( // creates a map `Map<Optional<Double>,FirstObject>`
firstObject -> secondDao.get(firstObject.getObjectId()).stream()
.filter(secondObject -> firstObject.getAttribute().equals(secondObject.getAttribute()))
.findFirst()
.map(FirstObject::getEfficiency),
Function.identity(),
(left, right) -> left // duplicated keys would appear when there are more then one object having no matching pair (the key would be an empty optional), so we need to guard against that case by providing a merge function to resulve duplicates
))
.entrySet().stream()
.filter(entry -> entry.getKey().isPresent())
.collect(Collectors.toMap(
entry -> entry.getKey().get(), // extracting a key (efficiency)
Map.Entry::getValue, // extracting a value
(left, right) -> { throw new AssertionError("duplicates are not expected"); }, // since the source is an intermediate map we don't expect duplicates
TreeMap::new
));
Another slightly more concise way of addressing this problem would be to create a custom collector by utilizing Collector.of()
(the overall logic remains the same):
NavigableMap<Double, FirstObject> myMap = firstDao.get(tableId).stream()
.collect(Collector.of(
TreeMap::new, // mutable container of the collector
(NavigableMap<Double, FirstObject> map, FirstObject firstObject) ->
secondDao.get(firstObject.getObjectId()).stream() // population the mutable container
.filter(secondObject -> next.getAttribute().equals(secondObject.getAttribute()))
.findFirst()
.map(FirstObject::getEfficiency)
.ifPresent(efficiency -> map.put(efficiency, firstObject)),
(left, right) -> { left.putAll(right); return left; } // merging containers in parallel, I'm assuming that there's no duplicates and hence there's no special policy
));
Sidenote: when you need a TreeMap
it's better to use NavigableMap
as an abstract type. This interface extends SortedMap
and offers a wide range of methods which are not accessible with SortedMap
.
This code provided in the question creates a stream of objects retrieved by the means of the firstDao
and the following filter
will update the map myMap
if the predicate is meat by adding a new entry (or replacing the value of the existing one) with efficiency
of the very first encountered matching object from the stream created using secondDao.get()
as a source.
.filter(firstobject -> secondDao.get().stream()
.anyMatch(secondObject -> {
if (someCondition) {
myMap.put(secondObject.getEfficiency(), firstObject);
}
return someCondition;
}))
anyMatch
- is a short-circuit operation and in case if there are the same id
but different efficiency
, they will be ignored.
This snippet from the solution above behaves in exactly the same way: findFirst
will pick the very first object for which someCondition
will be evaluated to true
(the case with an empty optional is being treated separately afterwards, and equals/hashCode
of the optional is delegates to equals/hashCode
implementation of its value, so there's no room for discrepancies in the result).
firstObject -> secondDao.get().stream()
.filter(secondObject -> someCondition)
.findFirst()
.map(FirstObject::getEfficiency)
And I assume it's the expected behavior because there is no mention that code provided in the question is malfunctioning in some ways, as well as there's no sample data (which might lead to the opposite conclusion).
Hence, the claim that the above solution doesn't produce the expected result contradicts the current state of the question, since it's based on the logic of the original code.
Until the desired result isn't specified (by providing a sample data, or describing what's wrong with the original code) I see no possibility to improve the answer.
Upvotes: 5