3

Say we have a Map<Key, Collection<Value>> myMap and a method that removes a value from the collection associated to a key. If removing a value leaves the collection empty, we'd want to get rid of the key entry in the map:

List<Value> removeValue(Key key, Value value) {
    List<Value> v = myMap.get(key);
    if (v != null) {
        v.remove(value);
        if (v.isEmpty())
            myMap.remove(key);
    }
    return v;
}

Is there any Java 8 way to achieve the described behavior with a one-liner or shorter method?

Holger
  • 285,553
  • 42
  • 434
  • 765
dabadaba
  • 9,064
  • 21
  • 85
  • 155
  • 1
    Nothing better comes to mind immediately. This might be a better fit on http://codereview.stackexchange.com/ though. – markspace May 09 '16 at 21:52
  • Your original method seems wrong, but it doesn't seem to be too verbose anyway... – M A May 09 '16 at 21:52
  • 1
    `if (v.size() == 1) myMap.remove(key);` looks wrong. What if the list does not contain the value? – smac89 May 09 '16 at 22:00
  • 1
    You may benefit from [this question](http://stackoverflow.com/q/1062960/5743988) to learn about the MulitMap in some libraries (not a duplicate) – 4castle May 09 '16 at 22:00

1 Answers1

9

You can use computeIfPresent for this:

static <K, V> List<V> removeValue(K key, V value, Map<K, List<V>> map){
    return map.computeIfPresent(key, (k, l) -> l.remove(value) && l.isEmpty() ? null : l);
}

computeIfPresent applies the BiFunction to the key and the current value in the map if the value is not null (if it is null computeIfPresent returns null immediately) and then either sets the value to the return value of the BiFunction if the return value is not null or removes the key from the map if the return value is null and finally returns the new value.

Note that it behaves slightly differently to the method in you question - it will not remove an already empty List from the map because remove will return false. If you want to remove an already empty List you could use l.isEmpty() || (l.remove(value) && l.isEmpty()).

Alex - GlassEditor.com
  • 14,957
  • 5
  • 49
  • 49
  • Thank you! Weird that I understand better the second form (which is what I was looking for precisely) than the first. Also, could something similar be done for putting elements? I can't think how because I see that both `computeIfPresent` and `computeIfAbsent` should be used. Or can it be one-lined as well? – dabadaba May 09 '16 at 22:13
  • 1
    @dabadaba if you want to create the list if it is not present then add to it you could use `map.computeIfAbsent(key, k -> new ArrayList<>()).add(v)` – Alex - GlassEditor.com May 09 '16 at 22:23
  • @dabadaba For that you should just use [`compute`](https://docs.oracle.com/javase/8/docs/api/java/util/Map.html#compute-K-java.util.function.BiFunction-) so that its okay for it to be `null`. Like `myMap.compute(key, (k, l) -> { if (l == null) l = new ArrayList<>(); l.add(value); return l; });` – 4castle May 09 '16 at 22:23
  • By the way I just tested your solution and `||` removes all elements from the list and wipes it. It should be `&&` instead. – dabadaba May 09 '16 at 22:30
  • So the trick is that when `l.isEmpty()` then `null` is returned and `computeIfPresent` clears the mapping to `key`? That's the whole **it** to removing the key right? – dabadaba May 09 '16 at 22:34
  • 1
    @dabadaba Yes, and I made a mistake with the `||` part. – Alex - GlassEditor.com May 09 '16 at 22:35