How to swap keys and values in a Map elegantly
Asked Answered
E

10

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 :)

Expositor answered 14/12, 2010 at 7:55 Comment(3)
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.Huntsville
All you need is a single loop with a single line is simple and elegent. IMHO. Java is not a functional language.Horseshit
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.Expositor
L
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.

Lawless answered 14/12, 2010 at 8:0 Comment(0)
W
59
Map<String, Integer> map = new HashMap<>();
Map<Integer, String> swapped = map.entrySet().stream().collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey));
Winwaloe answered 7/2, 2017 at 13:46 Comment(7)
awesome solution.Yuri
This would be the best answer after the addition of Streams with Java 8Breathing
what if the values in the original map where in more than one key? you would loose the value when swapping & get an ExceptionRadmen
Map<Integer, List<String>> swapped = map.entrySet().stream().collect(Collectors.groupingBy(Map.Entry::getValue, Collectors.mapping(Map.Entry::getKey, Collectors.toList())));Longer
@Longer omg, this would have taken me a whole day of failing!Bureaucrat
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?Connivance
@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 awayRaddy
F
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;
}
Fransen answered 14/12, 2010 at 8:7 Comment(0)
L
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.

Lawless answered 14/12, 2010 at 8:0 Comment(0)
I
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.

Insinuate answered 14/12, 2010 at 7:59 Comment(0)
S
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.

Smalt answered 14/12, 2010 at 8:0 Comment(0)
D
1

As a hint to answer https://mcmap.net/q/554010/-how-to-swap-keys-and-values-in-a-map-elegantly

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);
Declamatory answered 6/10, 2017 at 14:37 Comment(0)
C
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").

Confute answered 28/9, 2018 at 8:48 Comment(0)
M
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

Marseilles answered 20/4, 2021 at 11:38 Comment(0)
M
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()); 
    } 
}
Matrilineage answered 21/2, 2019 at 10:12 Comment(0)
M
0

This method will swap keys with values, regardless of the data types used in the map.

public static <K,V> Map<V,K> reverseOneToOneMap(Map<K,V> map) {
    return map.entrySet().stream().collect(Collectors.toMap(
            Map.Entry::getValue, Map.Entry::getKey));
}

IllegalStateException is thrown if not all values are unique (and therefore cannot become unique keys).

Mussel answered 30/5 at 5:28 Comment(3)
Before answering a 14 year old question, you should go through the previous answers so that you do not end up unnecessarily repeating the exact same thing that has been replied five times already.Remy
@Torben, sorry you don't feel my answer adds anything. I did look at every previous answer before I answered. Only one answered in a way that wasn't type-specific, but that one had a few issues and didn't use streams. The question asked for elegance and I feel my answer is the closest to that with the inclusion of generics, although I acknowledge that the inner body of the method is the same as the answer by Nikita.Mussel
Ok, thaty is good. for future rerference, if your intention is to provide an answer about a generic approach, it would be useful to describe it in the text with a bit more detail. At the moment people who are not fluent with generics will not understand your answer and people who understand generics consider your answer to be repetitive.Remy

© 2022 - 2024 — McMap. All rights reserved.