How do I convert lambda parameters to usable objects? - java

I'm trying to stream a list of Doubles into a Map<Double, Double>, where the keys are the Doubles in the original list, and the values are some computed value.
This is what my code looks like:
// "values" is a List<Double> that was passed in
ImmutableMap<Double, Double> valueMap = values.parallelStream()
.collect(Collectors.toMap(p -> p, p -> doThing(values, p)));
private Double doThing(List<Double>, Double p) {
Double computedValue = 0.0;
// Do math here with p
return computedValue;
}
However, IntelliJ is complaining that p -> p is not a valid lambda expression - it's complaining about a cyclic inference. I'm also getting an error when I call doThing, because p is a lambda parameter and not a Double.
When I try to cast p to a Double in the call to doThing, that fails to cast.
Am I missing something really obvious? It seems like there's no way to actually do anything with my lambda parameters...

I think the problem here is simply that your collecting operation returns a Map<Double, Double> but the type of valueMap is ImmutableMap<Double, Double>.
You also forgot to give a name for the List<Double> parameter in doThing but I assume it was only a typo when writing the question.

As stated, the problem is that Collectors.toMap accumulates the element into Map<K,U> (the current implementation returns an HashMap but it's an internal detail).
If you want to collect into an ImmutableMap, you can use the collectingAndThen collector.
Use Collectors.toMap as the downstream collector and provide the function map -> ImmutableMap.copyOf(map) as a finisher.
ImmutableMap<Double, Double> valueMap = values.stream()
.collect(collectingAndThen(toMap(p -> p, p -> doThing(values, p)), ImmutableMap::copyOf));

Related

How to convert a List of objects into a Map<String, Integer> with auto-generated values?

I have List<City> cities. I need to convert cities into a map Map<String, Integer>, where the value (Integer) has to be auto-generated.
I tried this, but it seems not allowed to use counter like that because of atomic error. Ho to solve this task?
public Map<String, Integer> convertListToMap(List<City> cities) {
Integer counter=0;
return cities.stream().forEach(elem->tollFreeVehicles.put(elem.getName(), counter++));
}
Local variables that are allowed to be used in the lambda expressions needs to be final or effectively final.
Have a look at the Oracle's tutorial on lambdas. A short excerpt:
a lambda expression can only access local variables and parameters of
the enclosing block that are final or effectively final. In this
example, the variable z is effectively final; its value is never
changed after it's initialized.
You can construct a map from a list using indices of items in the list as values by utilizing IntStream.range().
Also note that forEach() doesn't return a value. In order to generate a map as a result of the execution of the stream pipeline that will be returned from the method, you need to use collect() as terminal operation.
Map<String, Integer> result =
IntStream.range(0, cities.size())
.boxed()
.collect(Collectors.toMap(i -> cities.get(i).getName(),
Function.identity()));

From Map<String, Set<Integer>> collect values from a Set<String> of keys

I am trying to use map to map a set of keys into a Map of String to Set of Integer. Ideally I want to get all the value sets and collect them into a single set.
Lets say I have:
Map<String, List<Integer>> keyValueMap = new HashMap<>();
Set<String> keys = new HashSet<>();
Set<String> result = new HashSet<>();
I have tried:
result.addAll(keys.stream().map(key -> keyValueMap.get(key)).collect(Collectors.toSet());
This nets me an error saying addAll() is not applicable for the type Set>. I have tried replacing map() with flatMap() but I can't seem to get the syntax right if that is the solution.
What is the correct syntax to make this work?
Thanks!
It looks like the type of result should be Set<Integer> instead of Set<String>.
With your snippet, you're attempting to invoke Set#addAll on a Set<Integer>, but the argument being passed is a Set<List<Integer>>, which doesn't compile.
To ameliorate your issue, one solution is to use flatMap instead of map:
result.addAll(keys.stream()
.flatMap(key -> keyValueMap.get(key).stream())
.collect(Collectors.toSet()));
A logically equivalent snippet is:
result.addAll(keys.stream()
.map(keyValueMap::get)
.flatMap(List::stream)
.collect(Collectors.toSet()));
Another solution would be to utilize Map#values:
result.addAll(keyValueMap.values().stream()
.flatMap(List::stream)
.collect(Collectors.toSet()));

Summing up BigDecimal in a map of list of objects in Java

I'm stuck with some elegant ways to get a summation of BigDecimals in a map. I know how to calculate the sum in a map of BigDecimal but not a List of object with a BigDecimal.
The structure of my objects are as below:
Class Obj {
private BigDecimal b;
// Getter for b, say getB()
}
Map<String, List<Obj>> myMap;
I need to get a sum of all bs in myMap. Looking for some elegant ways to do this in Java, may be using streams?
Stream the values of the Map.
Use flatMap to flatten the stream of lists of BigDecimals to a stream of BigDecimals.
Use map to extract the BigDecimals.
Use reduce with a summing operation.
BigDecimal sum = myMap.values().stream()
.flatMap(List::stream)
.map(Obj::getB)
.reduce(BigDecimal.ZERO, (a, b) -> a.add(b) );
The Stream.reduce method takes an identity value (for summing values, zero), and a BinaryOperator that adds intermediate results together.
You may also use a method reference in place of the lambda above: BigDecimal::add.
BigDecimal sum = myMap.values()
.stream()
.flatMap(Collection::stream)
.map(Obj::getB)
.reduce(BigDecimal.ZERO, BigDecimal::add);

how can i create a HashMap to iterate inside lambda function?

Is there any way to create this hashmap inside the lambda function?
Map<SaleStatus, Long> sales = new HashMap<>();
saleStatusCounters.forEach(saleStatusCounter -> sales.put(saleStatusCounter.getStatus(), saleStatusCounter.getMatches()));
Something like this:
saleStatusCounters.stream()
.map(obj -> new HashMap<SaleStatus, Long>().put(obj.getStatus(), obj.getMatches()))
.collect(Collectors.toMap(???)));
Your code is fine as is. You can, nonetheless, use streams and Collectors.toMap to get the result you want:
Map<SaleStatus, Long> sales = saleStatusCounters.stream()
.collect(Collectors.toMap(obj -> obj.getStatus(), obj -> obj.getMatches()));
Note: this works as long as there are no collisions in the map, i.e. as long as you don't have two or more sale status counter objects with the same status.
In case you have more than one element in your list with the same status, you should use the overloaded version of Collectors.toMap that expects a merge function:
Map<SaleStatus, Long> sales = saleStatusCounters.stream()
.collect(Collectors.toMap(
obj -> obj.getStatus(),
obj -> obj.getMatches(),
Long::sum));
Here Long::sum is a BinaryOperator<Long> that merges two values that are mapped to the same key.

Java 8 stream "Cannot use this in a static context"

I am new to java8 stream & sorry about the stupid question . Here is my code which i am trying to create a map of id & value, but i am getting this error, not able to fix. Can anyone help me what is the alternative?
public static Map<Integer, String> findIdMaxValue(){
Map<Integer, Map<String, Integer>> attrIdAttrValueCountMap = new HashMap<>();
Map<Integer, String> attrIdMaxValueMap = new HashMap<>();
attrIdAttrValueCountMap.forEach((attrId, attrValueCountMap) -> {
attrValueCountMap.entrySet().stream().sorted(this::compareAttrValueCountEntry).findFirst().ifPresent(e -> {
attrIdMaxValueMap.put(attrId, e.getKey());
});
});
}
and sorting method
public static int compareAttrValueCountEntry(Map.Entry<String, Integer> e1, Map.Entry<String, Integer> e2) {
int diff = e1.getValue() - e2.getValue();
if (diff != 0) {
return -diff;
}
return e1.getKey().compareTo(e2.getKey());
}
I am getting this error
"Cannot use this in a static context"
There are several issues with your code. While this::compareAttrValueCountEntry would be easy to
fix by changing it to ContainingClassName::compareAttrValueCountEntry, this method is unnecessary
as there are several factory methods like Map.Entry.comparingByKey, Map.Entry.comparingByValue,
Comparator.reversed and Comparator.thenComparing, which can be combined to achieve the same goal
This guards you from the errors made within compareAttrValueCountEntry. It’s tempting to compare int
values by subtracting, but this is error prone as the difference between two int values doesn’t always
fit into the int range, so overflows can occur. Also, negating the result for reversing the order is
broken, as the value might be Integer.MIN_VALUE, which has no positive counterpart, hence, negating it
will overflow back to Integer.MIN_VALUE instead of changing the sign.
Instead of looping via forEach to add to another map, you may use a cleaner stream operation producing
the map and you can simplify sorted(…).findFirst() to min(…) which in not only shorter, but a
potentially cheaper operation.
Putting it together, we get
Map<Integer, String> attrIdMaxValueMap =
attrIdAttrValueCountMap.entrySet().stream()
.filter(e -> !e.getValue().isEmpty())
.collect(Collectors.toMap(Map.Entry::getKey,
e -> e.getValue().entrySet().stream()
.min(Map.Entry.<String, Integer>comparingByValue().reversed()
.thenComparing(Map.Entry.comparingByKey())).get().getKey()));
Note that I prepended a filter operation rejecting empty maps, which ensures that there will always be
a matching element, so there is no need to deal with ifPresent or such alike. Instead, Optional.get
can be called unconditionally.
Since this method is called findIdMaxValue, there might be a desire to reflect that by calling max
on the Stream instead of min, wich is only a matter of which comparator to reverse:
Map<Integer, String> attrIdMaxValueMap =
attrIdAttrValueCountMap.entrySet().stream()
.filter(e -> !e.getValue().isEmpty())
.collect(Collectors.toMap(Map.Entry::getKey,
e -> e.getValue().entrySet().stream()
.max(Map.Entry.<String, Integer>comparingByValue()
.thenComparing(Map.Entry.comparingByKey(Comparator.reverseOrder())))
.get().getKey()));
Unfortunately, such constructs hit the limitations of the type inference, which requires us to either,
use nested constructs (like Map.Entry.comparingByKey(Comparator.reverseOrder()) instead of
Map.Entry.comparingByKey().reversed()) or to insert explicit types, like with
Map.Entry.<String, Integer>comparingByValue(). In the second variant, reversing the second comparator,
we are hitting the litimation twice…
In this specific case, there might be a point in creating the comparator only once, keeping it in a variable and reuse it within the stream operation:
Comparator<Map.Entry<String, Integer>> valueOrMinKey
= Map.Entry.<String, Integer>comparingByValue()
.thenComparing(Map.Entry.comparingByKey(Comparator.reverseOrder()));
Map<Integer, String> attrIdMaxValueMap =
attrIdAttrValueCountMap.entrySet().stream()
.filter(e -> !e.getValue().isEmpty())
.collect(Collectors.toMap(Map.Entry::getKey,
e -> e.getValue().entrySet().stream().max(valueOrMinKey).get().getKey()));
Since the method compareAttrValueCountEntry is declared static,
replace the method reference
this::compareAttrValueCountEntry
with
<Yourclass>::compareAttrValueCountEntry

Categories