Reputation: 541
I have a Map<String, Double>
, and want to multiply all the values in the map by 2, say, but keep the nulls as nulls.
I can obviously use a for loop to do this, but was wondering if there was a cleaner way to do so?
Map<String, Double> someMap = someMapFunction();
Map<String, Double> adjustedMap = new Hashmap<>();
if (someMap != null) {
for (Map.Entry<String,Double> pair : someMap.entryset()) {
if (pair.getValue() == null) {
adjustedMap.put(pair.getKey(), pair.getValue());
} else {
adjustedMap.put(pair.getKey(), pair.getValue()*2)
}
}
}
Also sometimes the map returned by someMapFunction
is an immutable map, so this can't be done in place using Map.replaceAll
. I couldn't come up with a stream solution that was cleaner.
Upvotes: 48
Views: 9167
Reputation: 9086
If you are OK with Optional
values the following may work for you:
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import static java.util.stream.Collectors.toMap;
public static Map<String, Optional<Double>> g(Map<String, Double> map, Function<Double, Double> f) {
return map.entrySet().stream().collect(
toMap(
e -> e.getKey(),
e -> e.getValue() == null ? Optional.empty() : Optional.of(f.apply(e.getValue()))
));
}
and then:
public static void main(String[] args) throws Exception {
Map<String, Double> map = new HashMap<>();
map.put("a", 2.0);
map.put("b", null);
map.put("c", 3.0);
System.out.println(g(map, x -> x * 2));
System.out.println(g(map, x -> Math.sin(x)));
}
prints:
{a=Optional[4.0], b=Optional.empty, c=Optional[6.0]}
{a=Optional[0.9092974268256817], b=Optional.empty, c=Optional[0.1411200080598672]}
This is fairly clean with creation of the new map delegated to Collectors
and the added benefit of the return type Map<String, Optional<Double>>
clearly indicating the possibility null
s and encouraging users to handle them.
Upvotes: 1
Reputation: 2805
You can achieve that by converting into a stream, with something like:
someMap.entrySet()
.forEach(entry -> {
if (entry.getValue() != null) {
adjustedMap.put(entry.getKey(), someMap.get(entry.getKey()) * 2);
} else {
adjustedMap.put(entry.getKey(), null);
}
});
which can be shortened to:
someMap.forEach((key, value) -> {
if (value != null) {
adjustedMap.put(key, value * 2);
} else {
adjustedMap.put(key, null);
}
});
So, if you have a map with:
Map<String, Double> someMap = new HashMap<>();
someMap.put("test1", 1d);
someMap.put("test2", 2d);
someMap.put("test3", 3d);
someMap.put("testNull", null);
someMap.put("test4", 4d);
You will get this output:
{test4=8.0, test2=4.0, test3=6.0, testNull=null, test1=2.0}
Upvotes: 5
Reputation: 54639
There already are many answers. Some of them seem a bit dubious to me. In any case, most of them inline the null
-check in one form or the other.
An approach that takes one step up the abstraction ladder is the following:
You want to apply an unary operator to the values of the map. So you can implement a method that applies an unary operator to the values of the map. (So far, so good). Now, you want a "special" unary operator that is null
-safe. Then, you can wrap a null
-safe unary operator around the original one.
This is shown here, with three different operators (one of them being Math::sin
, for that matter) :
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.UnaryOperator;
public class MapValueOps
{
public static void main(String[] args)
{
Map<String, Double> map = new LinkedHashMap<String, Double>();
map.put("A", 1.2);
map.put("B", 2.3);
map.put("C", null);
map.put("D", 4.5);
Map<String, Double> resultA = apply(map, nullSafe(d -> d * 2));
System.out.println(resultA);
Map<String, Double> resultB = apply(map, nullSafe(d -> d + 2));
System.out.println(resultB);
Map<String, Double> resultC = apply(map, nullSafe(Math::sin));
System.out.println(resultC);
}
private static <T> UnaryOperator<T> nullSafe(UnaryOperator<T> op)
{
return t -> (t == null ? t : op.apply(t));
}
private static <K> Map<K, Double> apply(
Map<K, Double> map, UnaryOperator<Double> op)
{
Map<K, Double> result = new LinkedHashMap<K, Double>();
for (Entry<K, Double> entry : map.entrySet())
{
result.put(entry.getKey(), op.apply(entry.getValue()));
}
return result;
}
}
I think this is clean, because it nicely separates the concerns of applying the operator and performing the null
-check. And it is null
-safe, because ... the method name says so.
(One could argue to pull the call to wrap the operator into a nullSafe
one into the apply
method, but that's not the point here)
Edit:
Depending on the intended application pattern, one could do something similar and apply the transformation in place, without creating a new map, by calling Map#replaceAll
Upvotes: 6
Reputation: 10707
You can do this with this code:
Map<String, Double> map = new HashMap<>();
map.put("1", 3.0);
map.put("3", null);
map.put("2", 5.0);
Map<String, Double> res =
map.entrySet()
.stream()
.collect(
HashMap::new,
(m,v)->m.put(v.getKey(), v.getValue() != null ? v.getValue() * 2 : null),
HashMap::putAll
);
System.out.println(res);
and the output will be:
{1=6.0, 2=10.0, 3=null}
It will allow you to keep null
values in the map.
Upvotes: 3
Reputation: 34460
Yet another way:
Map<String, Double> someMap = someMapFunction();
int capacity = (int) (someMap.size() * 4.0 / 3.0 + 1);
Map<String, Double> adjustedMap = new HashMap<>(capacity);
if (someMap != null) someMap.forEach((k, v) -> adjustedMap.put(k, v == null ? v : v * 2));
Note that I'm building the new map with the default load factor (0.75 = 3.0 / 4.0
) and an initial capacity that is always greater than size * load_factor
. This ensures that adjustedMap
is never resized/rehashed.
Upvotes: 2
Reputation: 120848
How about this?
Map<String, Double> adjustedMap = new HashMap<>(someMap);
adjustedMap.entrySet().forEach(x -> {
if (x.getValue() != null) {
x.setValue(x.getValue() * 2);
}
});
Upvotes: 4
Reputation: 393791
My first instinct was to suggest a Stream
of the input Map
's entrySet
which maps the values to new values and terminates with collectors.toMap()
.
Unfortunately, Collectors.toMap
throws NullPointerException
when the value mapper function returns null
. Therefore it doesn't work with the null
values of your input Map
.
As an alternative, since you can't mutate your input Map
, I suggest that you create a copy of it and then call replaceAll
:
Map<String, Double> adjustedMap = new HashMap<>(someMap);
adjustedMap.replaceAll ((k,v) -> v != null ? 2*v : null);
Upvotes: 64
Reputation: 18792
To keep null values you can use something as simple as :
someMap.keySet()
.stream()
.forEach(key -> adjustedMap.put(key, (someMap.get(key)) == null ? null : someMap.get(key) * 2));
Edit in response to Petr Janeček comment: you could apply the proposed on a copy of someMap
:
adjustedMap.putAll(someMap);
adjustedMap.keySet()
.stream()
.forEach(key -> adjustedMap.put(key, (adjustedMap.get(key)) == null ? null : adjustedMap.get(key) * 2));
Upvotes: 2
Reputation: 703
Use like this.
Map<String, Double> adjustedMap = map.entrySet().stream().filter(x -> x.getValue() != null)
.collect(Collectors.toMap(x -> x.getKey(), x -> 2*x.getValue()));
//printing
adjustedMap.entrySet().stream().forEach(System.out::println);
Upvotes: -1
Reputation: 38424
As an alternative to streaming and/or copying solutions, the Maps.transformValues()
utility method exists in Google Guava:
Map<String, Double> adjustedMap = Maps.transformValues(someMap, value -> (value != null) ? (2 * value) : null);
This returns a lazy view of the original map that does not do any work on its own, but applies the given function when needed. This can be both a pro (if you're unlikely to ever need all the values, this will save you some computing time) and a con (if you'll need the same value many times, or if you need to further change someMap
without adjustedMap
seeing the changes) depending on your usage.
Upvotes: 25
Reputation: 137
Try something like this with java 8 steam api
Map<String, Double> newMap = oldMap.entrySet().stream()
.collect(Collectors.toMap(x -> x.getKey(), x -> x.getValue() == null ? null: x.getValue()*2));
Upvotes: -1
Reputation: 2154
It can be done like that
someMap.entrySet().stream()
.filter(stringDoubleEntry -> stringDoubleEntry.getValue() != null) //filter null values out
.forEach(stringDoubleEntry -> stringDoubleEntry.setValue(stringDoubleEntry.getValue() * 2)); //multiply values which are not null
In case you need a second map where just values in which are not null just use the forEach
to put them into your new map.
Upvotes: 4