4

I am seaching for a data structure that is almost exactly a HashMap<String,Integer>, but the problem with HashMaps is that most of the data stored in key value pairs is lost by calling the putAll() method on two HashMaps, due to the replacement behavior of putVal() in line 655 of the java/util/HashMap.java.

This is basically the change that I want:

    if (e != null) { // existing mapping for key
         V oldValue = e.value;
         if (!onlyIfAbsent || oldValue == null)
--            e.value = value;
++            e.value = value + oldValue;
         afterNodeAccess(e);
         return oldValue;
    }

Is there an existing data structure, that I've overlooked that would do such a thing, or how do I create a class that is basically a HashMap with that one change?

I've already tried to code something, but doen't work how I want it to... In fact it doen't matter if I set the put method on @Override, do it like that, or delete it completely - the replacing behavior ofcourse stays the same, because putAll() uses putVal() that I can't reach / change from the outside - or I at least don't know how...

 /**
  * doesn't work, putAll() uses putVal() that I can't reach
  */
 public class SumHashMap<K> extends HashMap<K, Integer> {
    private static final long serialVersionUID = 1L;

    public Integer put(K key, Integer value) {
        Integer oldValue = get(key);
        if (oldValue == null)
            return super.put(key, value);
        return super.put(key, oldValue + value);
    }
}

Thanks in advance

Additional info:

  • I want to use the putAll() function in the reduction of a stream out of custom HashMaps.
  • If I have two custom HashMaps of this sort {"key1" : 2, "key3" : 4} and {"key3" : 1} the result of a.putAll(b) should be {"key1" : 2, "key3" : 5}
1
  • Instead of creating such a data structure, make that put function a static helper function. Data structures are made to be generic. It would be unmaintainable when you'll have to create a new hashmap class with a slight variation. Commented Jan 24, 2021 at 21:00

3 Answers 3

2

You don't need a new data structure for this, you don't even need a new class that inherits from HashMap. Instead, use the Map.merge method:

newMap.forEach((k, v) -> oldMap.merge(k, v, Integer::sum));

This code uses Map.forEach to traverse the entries of the new map (the one you'd receive as an argument in putAll) and uses Map.merge (along with Integer::sum) to merge its entries into an already existing map (which I've named oldMap here).

Sign up to request clarification or add additional context in comments.

1 Comment

If you use ConcurrentHashMap, then a single merge will be thread-safe without any extra effort. It is unclear if thread-safety is an (implied) requirement.
0

I think this is what you are looking for. I made it so that the key can be any type. If you want, you can remove the generic for the key and just extend HashMap<String, Integer>.

import java.util.HashMap;
import java.util.Map;

public class AddingHashMap<K> extends HashMap<K, Integer> {
    @Override
    public Integer put(K key, Integer value) {
        Integer existingValue = super.get(key);
        if (existingValue == null) {
            existingValue = value;
        } else {
            existingValue = existingValue.intValue() + value.intValue();
        }
        return super.put(key, existingValue);
    }

    @Override
    public void putAll(Map<? extends K, ? extends Integer> m) {
        m.entrySet().forEach(entry -> {
            this.put(entry.getKey(), entry.getValue());
        });
    }
}

Here is it working:

public static void main(String[] argv) {
        AddingHashMap<String> myAddingHashMap = new AddingHashMap<>();
        myAddingHashMap.put("One", 1);
        myAddingHashMap.put("Two", 2);
        myAddingHashMap.put("One", 3);

        myAddingHashMap.entrySet().forEach(entry -> System.out.println(entry.getKey() + " - " + entry.getValue()));
    }

Outputs:

One - 4
Two - 2

Later edit: Keep in mind that this is NOT thread-safe.

6 Comments

Thx, your elegant way to redefine putAll() was even more than I was hoping for! Works like a charm :)
@doej1367 Glad I could help. Keep in mind that this is just a fast implementation to show the direction. It is not thread-safe, it does not handle null values and there may be other problems. Also, let me know if you need any clarifications on the implementation.
1) as far as I remember what thread-safety means, its quite the big topic. In this case, is it only the order that get's mixed up a bit or could it pose some other problems I don't see yet... for example if i use the parallel() method on my stream where I use the putAll() to reduce it - that could be nasty. But I don't, so it should be fine as well, right? 2) null values are handled at another point in my code and / or aren't in the data set, so that's fine. Thx, for mentioning it though.
Subclassing HashMap is not necessary nor desirable. There are many methods in the Map interface that facilitate this type of requirement.
@WJS Why isn't it desirable? The fact that OP needs the putAll() methods is a sign that he already uses HashMaps to store the non-processed data. By extending it you get all the needed functionalities that he is used to. He would still have to use a HashMap internally to store the processed data, but having the new class extend HashMap you don't have to complicate things but re-writting some methods like get().
|
-1

I don't think there is a datastructure that does that. The purpose of the datastructure is to store data, not to have logic associated to it. The HashMap can store key-value pairs for you but if you need some more advanced, or specific, logic associated with certain operations, you'll need to add it yourself.

One way is to wrap the map in a class which has this logic. Another might be to implenent the Map interface yourself (which could also use a HashMap internally) though I would not recommend that since changing the behaviour is not a great idea.

A minimal wrapper providing adding functionality:

public class AddingMap {
    private final HashMap<String, Integer> map;

    public AddingMap() {
        map = new HashMap<>();
    }

    public void add(String key, Integer value) {
        map.put(key, map.getOrDefault(key, 0) + value);
    }
    
    public Integer get(String key) {
        return map.get(key);
    }
}

Edit

Shouldn't have finished writing the answer half way...

Indeed, the addAll() method is missing:

public void addAll(Map<String, Integer> map) {
    map.entrySet().forEach(e -> this.add(e.getKey(), e.getValue()));
}

2 Comments

Thy, for the fast reply. Already thought about a wrapper function, but what happens if I call putAll() on this one? Wouldn't that replace values again?
This is not what the OP was asking. Using this implementation, the problem with putAll() still persists.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.