23

I already know how to do it the hard way and got it working - iterating over entries and swapping "manually". But i wonder if, like so many tasks, this one can be solved in a more elegant way.

I have read this post, unfortunately it does not feature elegant solutions. I also have no possibility to use any fancy Guava BiMaps or anything outside the jdk (project stack is already defined).

I can assume that my map is bijective, btw :)

Community
  • 1
  • 1
kostja
  • 60,521
  • 48
  • 179
  • 224
  • 4
    Adding an extra utility library isn't really changing the project "stack" in the way that (say) changing which UI framework you're using would be. I would urge you to reconsider your opposition to using Guava if at *all* possible. – Jon Skeet Dec 14 '10 at 07:57
  • All you need is a single loop with a single line is simple and elegent. IMHO. Java is not a functional language. – Peter Lawrey Dec 14 '10 at 08:07
  • thank you all, with all those overlapping and concise answers one really has a hard time choosing which one to accept. I guess Ill go with Aaron Digulla for providing a wrapper workaround. – kostja Dec 14 '10 at 08:22

9 Answers9

58
Map<String, Integer> map = new HashMap<>();
Map<Integer, String> swapped = map.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
Nikita Marshalkin
  • 756
  • 1
  • 5
  • 4
  • awesome solution. – CKM May 09 '17 at 07:52
  • This would be the best answer after the addition of Streams with Java 8 – afarrapeira Jul 16 '18 at 12:49
  • 2
    what if the values in the original map where in more than one key? you would loose the value when swapping & get an Exception – Nacho Nov 08 '18 at 14:58
  • 2
    Map> swapped = map.entrySet().stream().collect(Collectors.groupingBy(Map.Entry::getValue, Collectors.mapping(Map.Entry::getKey, Collectors.toList()))); – FreyaZ Dec 29 '19 at 13:02
  • @FreyaZ omg, this would have taken me a whole day of failing! – Natan Cox Nov 27 '20 at 19:59
  • 1
    Hi, I got an error saying "Non-static method cannot be referenced from a static context" for Map.Entry::getValue and Map.Entry::getKey, what to do now? – Jimmy Zhao Nov 24 '22 at 01:51
  • 1
    @JimmyZhao I had the same issue you probably had the second map as a Map implementation class such as HashMap change it to Map and the error should go away – Marquis Blount Jul 10 '23 at 15:15
24

If you don't have a choice to use a third party library, I don't consider the following code so ugly (though some scripting languages do have elegant ways of doing it):

//map must be a bijection in order for this to work properly
public static <K,V> HashMap<V,K> reverse(Map<K,V> map) {
    HashMap<V,K> rev = new HashMap<V, K>();
    for(Map.Entry<K,V> entry : map.entrySet())
        rev.put(entry.getValue(), entry.getKey());
    return rev;
}
Eyal Schneider
  • 22,166
  • 5
  • 47
  • 78
11

The standard API / Java runtime doesn't offer a bi-directional map, so the only solution is to iterate over all entries and swap them manually.

What you can do is create a wrapper class which contains two maps and which does a dual put() internally so you have fast two views on the data.

[EDIT] Also, thanks to open source, you don't have to include a third party library, you can simply copy the classes you need into your own project.

Emil
  • 13,577
  • 18
  • 69
  • 108
Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
3

Maps are not like lists, which can be reversed by swapping head with tail.

Objects in maps have a computed position, and using the value as key and the key as value would requiere to re-compute the storage place, essentialy building another map. There is no elegant way.

There are, however, bidirectional maps. Those may suit your needs. I'd reconsider using third-party libraries.

salezica
  • 74,081
  • 25
  • 105
  • 166
2

There are some jobs that can be simplified to a certain point and no more. This may be one of them!

If you want to do the job using Java collections apis only then brute force is the way to go - it will be quick (unless the collection is huge) and it will be an obvious piece of code.

Fortyrunner
  • 12,702
  • 4
  • 31
  • 54
1

As a hint to answer https://stackoverflow.com/a/42091477/8594421

This only works, if the map is not a HashMap and does not contain duplicate values.

Map<String,String> newMap = oldMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));

throws an exception

java.lang.IllegalStateException: Duplicate key

if there are values more than once.

The solution:

HashMap<String,String> newMap = new HashMap<>();

for(Map.Entry<String,String> entry : oldMap.entrySet())
        newMap.put(entry.getValue(), entry.getKey());

// Add inverse to old one
oldMap.putAll(newMap);
Rahul Sharma
  • 5,614
  • 10
  • 57
  • 91
User8461
  • 366
  • 2
  • 14
1

If you had access to apache commons-collections, you could have used MapUtils.invertMap.

Note: The behaviour in case of duplicated values is undefined.

(Replying to this as this is the first google result for "java invert map").

kebin
  • 11
  • 2
1

Java stream API provides nice set of APIs that would help you with this.

If the values are unique then the below would work. When I mean values, I mean the V in the Map<K, V>.

Map<String, Integer> map = new HashMap<>();
Map<Integer, String> swapped = map.entrySet()
                                  .stream()
                                  .collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));

If the values are not unique, then use below:

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

Thanks Nikita and FreyaZ. Posting as new answer as there were so many edit queues for Nikita's Answer

Andrews
  • 895
  • 3
  • 15
  • 30
0

This will work for duplicate values in the map also, but not for HashMap as values.

package Sample;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class Sample {
    public static void main(String[] args) { 
        Map<String,String> map = new HashMap<String,String>(); 
        Map<String, Set<String> > newmap = new HashMap<String, Set<String> >(); 

        map.put("1", "a"); 
        map.put("2", "a"); 
        map.put("3", "b"); 
        map.put("4", "b"); 
        System.out.println("before Reversing \n"+map.toString()); 

        for (Map.Entry<String, String> entry : map.entrySet()) 
        { 
            String oldVal = entry.getValue(); 
            String oldKey = entry.getKey(); 
            Set<String> newVal = null; 

            if (newmap.containsKey(oldVal)) 
            { 
                newVal = newmap.get(oldVal); 
                newVal.add(oldKey); 
            } 
            else 
            { 
                newVal= new HashSet<>(); 
                newVal.add(oldKey); 
            } 
            newmap.put(oldVal, newVal); 
        } 
        System.out.println("After Reversing \n "+newmap.toString()); 
    } 
}
Samuel Philipp
  • 10,631
  • 12
  • 36
  • 56