48

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.

user987339
  • 10,519
  • 8
  • 40
  • 45
Omar Haque
  • 541
  • 4
  • 9
  • 6
    Ah, how nice are null pointers... in a sensible language that has functors and type-safe option types, the solution to this problem would just be `fmap (fmap (*2))`, done. – leftaroundabout Oct 17 '18 at 11:10
  • 1
    @leftaroundabout In a sensible language that recognizes compositions of functors as functors, it should be just `fmap (*2)` for the combination of map and option... – Andrey Tyukin Oct 17 '18 at 12:05
  • 1
    @AndreyTyukin hm, interesting point, but is there any language that does this automatically and still also allows you to map _only_ into the outer functor when needed? Seems quite nontrivial to overload the choice of functor in this way. What certainly is sensible for a language is to allow you to easily wrap the composition of two functors into a new, custom functor. – leftaroundabout Oct 17 '18 at 12:12
  • 3
    @leftaroundabout Let's start a flamewar; Which one is "The Best Programming Language®"? ;-) But seriously: Java *does* have "functors" (at least, lambdas are pretty close...), and when there is the need for some abstraction, one can usually introduce it. The actual calls in [my answer](https://stackoverflow.com/a/52861841/3182664) are pretty similar to what you suggested. – Marco13 Oct 17 '18 at 19:03
  • 2
    @Marco13 yeah, I know I should resist my urge to post comments like that... — A [functor](https://en.wikipedia.org/wiki/Functor_(category_theory)) is actually something quite different from a lambda; you may be thinking of _function objects_ (which in C++ are incorrectly called “functors”). Anyway, that `nullSafe` adaptor you showed in your answer is pretty nice. (In fact it basically implements the option functor that Java implicitly carries around with its possibility of null references.) – leftaroundabout Oct 17 '18 at 19:16
  • @Marco13 No, please let's not do that... ;) That kind-of was my point: one could imagine a language where the same idea could be expressed twice as concise *even in comparison to Haskell*, so maybe we shouldn't count how many characters it takes. Let's just answer the question, solve the concrete problem at hand, but otherwise abstain from comparing code length in various languages. – Andrey Tyukin Oct 17 '18 at 20:42

12 Answers12

64

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);
Eran
  • 387,369
  • 54
  • 702
  • 768
  • 4
    a similar solution can be `someMap.forEach((k, v) -> adjustedMap.put(k, v == null ? null : 2*v));` after creating a new empty map; this avoids the copy/replacement of the values in the map – fantaghirocco Oct 17 '18 at 12:09
  • 1
    @Eran while this is correct, `replaceAll` is needed when there is an additional filter or computation based on the `Key`, which does not happen here. IMO this can [be simplified](https://stackoverflow.com/a/52857133/1059372) – Eugene Oct 17 '18 at 14:56
25

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.

Petr Janeček
  • 37,768
  • 12
  • 121
  • 145
6

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

Marco13
  • 53,703
  • 9
  • 80
  • 159
5

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}
grg
  • 5,023
  • 3
  • 34
  • 50
Leviand
  • 2,745
  • 4
  • 29
  • 43
  • 3
    Your `someMap.get(key)` can be simply `value` instead. Anyway, downvoting for style. It may be my personal subjective preference, but I strongly believe functional code should be side-effect free. Therefore, using `forEach()` on one map to change a different map is a no-no in my book. I'd prefer collecting the stream into a new map instead. That's the canonical and recognized way of consuming streams. – Petr Janeček Oct 17 '18 at 08:49
  • 6
    @PetrJaneček well `forEach` can only act by side-effects anyway, also this answer does *not* use `.stream()` anywhere, which makes a big difference... But this can be [simplified indeed](https://stackoverflow.com/a/52857133/1059372) – Eugene Oct 17 '18 at 14:35
4

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.

CodeMatrix
  • 2,124
  • 1
  • 18
  • 30
  • 1
    This solution does what the OP's code does in a nice form (though `stringDoubleEntry` is a bit unaccustomedly long). It exploits that the entries are backed by the map; no new map is created. – Joop Eggen Oct 17 '18 at 08:34
  • 1
    I did not want to criticize, indeed the other way around. There was no upvote and others were already upvoted, despite your solution being straight-forward efficient. – Joop Eggen Oct 17 '18 at 08:39
  • @JoopEggen but you don't even [need to stream](https://stackoverflow.com/a/52857133/1059372) – Eugene Oct 17 '18 at 14:33
  • @Eugene indeed `someMap.forEach(e -> if (e.getValue() != null) e.setValue....` is possible, but without `filter`. So `stream+filter+forEach` seems also a nice separation. There are sufficient nice answers to pick from. – Joop Eggen Oct 17 '18 at 14:40
  • 2
    This removes the `null` values. The question wants to keep them. – OrangeDog Oct 18 '18 at 10:52
  • 1
    @OrangeDog it doesn't. I just filter the nulls out to multiply the existing values. The null values will stay I don't create a new Map or remove them. In case of an collect it will remove the null values but at the moment it just filter the null values out to multiply the existing values. – CodeMatrix Oct 19 '18 at 08:12
  • @CodeMatrix sorry yes, you mutate the existing map, not collect a new one – OrangeDog Oct 19 '18 at 08:27
4

How about this?

Map<String, Double> adjustedMap = new HashMap<>(someMap);
adjustedMap.entrySet().forEach(x -> {
      if (x.getValue() != null) {
            x.setValue(x.getValue() * 2);
      }
});
Eugene
  • 117,005
  • 15
  • 201
  • 306
3

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.

user987339
  • 10,519
  • 8
  • 40
  • 45
  • Yep. That does it. +1 – c0der Oct 17 '18 at 08:58
  • @Eugene That's an upside, too. OP said that _"the map returned by `someMapFunction` is an immutable map, so this can't be done in place using `Map.replaceAll`"_. – Petr Janeček Oct 17 '18 at 14:34
  • 1
    @PetrJaneček I've removed that comment, the only downside in this case is potentially the resizes of the `HashMap` – Eugene Oct 17 '18 at 14:34
2

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));
c0der
  • 18,467
  • 6
  • 33
  • 65
  • You should absolutely stream on the `entrySet()`, they you'd avoid calling `someMap.get(key)` in a loop, and would be able to just do `entry.value()` instead. Anyway, downvoting for style. It may be my personal subjective preference, but I strongly believe functional code should be side-effect free. Therefore, using `forEach()` on one map to change a different map is a no-no in my book. I'd prefer collecting the stream into a new map instead. That's the canonical and recognized way of consuming streams. – Petr Janeček Oct 17 '18 at 08:47
  • @PetrJaneček You could apply it to a `someMap` copy if you prefer that style. – c0der Oct 17 '18 at 08:53
2

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.

fps
  • 33,623
  • 8
  • 55
  • 110
1

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 nulls and encouraging users to handle them.

David Soroko
  • 8,521
  • 2
  • 39
  • 51
-1

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);
c0der
  • 18,467
  • 6
  • 33
  • 65
Pandey Amit
  • 657
  • 6
  • 19
-1

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));
Nicholas K
  • 15,148
  • 7
  • 31
  • 57
Oğuzhan Aygün
  • 137
  • 1
  • 1
  • 13