2

I need to invert an original map. which type is <Integer, String>, like {1 = A, 2 = A, 3 = B....}. I want to create a new map which is String to ArrayList because if 1 = A, and 2 = A, than I want to have something like this: A = [1, 2].

So how can I do that?

General Grievance
  • 4,555
  • 31
  • 31
  • 45
Anderson
  • 23
  • 4

2 Answers2

5

You can easily do it using Java 8's stream API, below is an example:

public static void main(String[] args) throws FileNotFoundException {

    Map<Integer, String> map = new HashMap<>();
    map.put(1, "A");
    map.put(2, "A");
    map.put(3, "B");

    Map<String, List<Integer>> invertedMap = map.entrySet()
    .stream()
    .collect(Collectors.groupingBy(Entry::getValue, 
            Collectors.mapping(Entry::getKey, Collectors.toList())));

    System.out.println(invertedMap);

}
Darshan Mehta
  • 30,102
  • 11
  • 68
  • 102
  • 1
    I really do not understand why people prefer using a streams approach for a problem like this. It seems to be unintuitive non-self documenting code. How is this better than the traditional looping example? – bhspencer Dec 06 '16 at 23:48
  • 1
    I'm not going to downvote because it isn't wrong, but I'd bounce this on a codereview. Way too hard to figure out what the code is doing and if you're requirements change there's no way to alter it in place, you'd have to completely rewrite it. – Gabe Sechan Dec 06 '16 at 23:50
  • 3
    General consesnsus here is, if Java already provides libraries/APIs for something, I would rather use that than writing my own boilerplate code (which won't be as efficient as Java's native APIs anyway). – Darshan Mehta Dec 07 '16 at 00:09
4

You can try this:

HashMap<Integer, String> original = new HashMap<>();
HashMap<String, ArrayList<Integer>> inverted = new HashMap<>();

original.put(1, "A");
original.put(2, "B");
original.put(3, "C");
original.put(4, "A");

for (Integer key: original.keySet()) {
    String newKey = original.get(key);

    inverted.computeIfAbsent(newKey, k -> new ArrayList<>());
    inverted.get(newKey).add(key);

}
System.out.println(original);
System.out.println(inverted);

So, let's say HashMap<Integer, String> original is {1=A, 2=B, 3=C, 4=A}, then you will get {A=[1, 4], B=[2], C=[3]}.

EDIT: If you want a more generic version, as @Mr.Polywhirl has suggested, you can use:

public static final <T, U> Map<U, List<T>> invertMap(Map<T, U> map) {
    HashMap<U, List<T>> invertedMap = new HashMap<>();

    for (T key : map.keySet()) {
        U newKey = map.get(key);

        invertedMap.computeIfAbsent(newKey, k -> new ArrayList<>());
        invertedMap.get(newKey).add(key);

    }

    return invertedMap;
}
lmiguelvargasf
  • 63,191
  • 45
  • 217
  • 228
  • The problem with this (as opposed to the original version you posted) is that this requires 2 hash lookups per index. If your hash is trivial like in the example, no biggie. If you have several hundred items in it, you ought to do it the other way for the speedup- there shouldn't be more than 1 get here (computeIfAbsent has to do a get to see if the item exists). – Gabe Sechan Dec 06 '16 at 23:47
  • @GabeSechan, feel free to edit my answer to make this code more efficient. I do not have a great knowledge of Java. – lmiguelvargasf Dec 06 '16 at 23:50
  • Here's a generic version of the code above. Feel free to add it to your response. http://pastebin.com/yTExx5Fi – Mr. Polywhirl Dec 07 '16 at 00:07
  • @Mr.Polywhirl, I have just done it, thank you so much, this is pretty useful since it is generic. – lmiguelvargasf Dec 07 '16 at 00:11
  • @GabeSechan `Map.computeIfAbsent` returns the value, whether existing or newly computed, so one can simply write: `invertedMap.computeIfAbsent(newKey, k -> new ArrayList<>()).add(key)`. – Stuart Marks Dec 08 '16 at 00:27
  • @StuartMarks Ah, didn't know that. In that case its a good solution, just remove the second get. – Gabe Sechan Dec 08 '16 at 00:29