Merging Two HashMaps With Duplicate Keys and Update Value - java

I have two HashMaps whose keys are String and whose values are MyObject. My goal is to merge these two hashmaps, and then return a hashmap containing all the objects from these to maps. But if the keys (Strings) are identical, instances with the same key should be stored as the same MyObject. I have successfully managed to do this.
My problem is that i´d like to update a field inside the instance with identical keys.
class MyObject
String name;
int counter;
MyObject(String name) {
this.name = name;
this.counter = 1;
}
// getters and setters...
My goal is to update the int counter every time the keys are identical in the hashmap.
This code will successfully merge these two maps, put i cant seem to find out how to update the field int counter in MyObject every time MyObject have the same keys.
class Main
HashMap<String, MyObject> map1 = new HashMap<>();
HashMap<String, MyObject> map2 = new HashMap<>();
//some code that will put some instances of MyObject into map1 and map2
// assume that least three instances have the same keys
HashMap<String, MyObject> map3 = new HashMap<>(map1);
map2.forEach((key, value) -> map3.merge(key, value, (v1, v2) -> new MyObject(v1.getName())));
Suppose these two objects have the same key;
MyObject.getCounter() = 3 in map1
MyObject.getCounter() = 5 in map2
The new MyObject.getCounter() should be 8.

You can do it as follows:
stream both entry sets.
collect to a map
and add them during the merge method
Create some data
HashMap<String, MyObject> map1 = new HashMap<>();
HashMap<String, MyObject> map2 = new HashMap<>();
map1.put("A", new MyObject(2));
map1.put("B", new MyObject(4));
map1.put("C", new MyObject(10));
map2.put("C", new MyObject(10));
map2.put("B", new MyObject(8));
Now create the map
Map<String, MyObject> result = Stream
.concat(map1.entrySet().stream(),
map2.entrySet().stream())
.collect(Collectors.toMap(Entry::getKey,
Entry::getValue, (a, b) -> {
a.setCounter(a.getCounter() + b.getCounter());
return a;
}, HashMap::new));
result.entrySet().forEach(System.out::println);
Prints
A=2
B=12
C=20
You could also use the following, prior to collection.
Map<String, MyObject> result = Stream.of(map1,map2)
.flatMap(map->map.entrySet().stream())
.collect(...);
Class definition
class MyObject {
int value;
public MyObject(int v) {
this.value = v;
}
public int getCounter() {
return value;
}
public void setCounter(int a) {
value = a;
}
public String toString() {
return value + "";
}
}

You have to set the counter value of the new MyObject that you create.
If the class MyObject has a setter,
Map<String, MyObject> map3 = new HashMap<>(map1);
map2.forEach((key, value) -> map3.merge(key, value, (v1, v2) -> {
MyObject myObject = new MyObject(v1.getName());
myObject.setCounter(v1.getCounter() + v2.getCounter());
return myObject;
}));
If you have an overloaded constructor accepting the name and the counter like,
MyObject(String name, int counter) {
this.name = name;
this.counter = counter;
}
then, you can do like,
map2.forEach((key, value) -> map3.merge(key, value,
(v1, v2) -> new MyObject(v1.getName(), v1.getCounter() + v2.getCounter())));

Related

Group map values but keys are same

I have a map like this. Map<long,List<Student>> studentMap
Key is a number 1,2,3,4...
Student object is :
public class Student {
private long addressNo;
private String code;
private BigDecimal tax;
private String name;
private String city;
// getter and setters`
}
What i want to do is to convert it Map<long,List<StudentInfo>> studentInfoMap object and group id, addressNo and code fields.I want key are same for both maps.
I can group the map by using these codes but summingDouble is not working for BigDecimal.Also I cannot convert my studentMap to studentInfoMap.:(
studentInfoMap.values().stream()
.collect(
Collectors.groupingBy(StudentInfo::getCode,
Collectors.groupingBy(StudentInfo::getAddressNo,
Collectors.summingDouble(StudentInfo::getTax))));
My studentInfo object is :
public class StudentInfo {
private long addressNo;
private String code;
private BigDecimal tax;
// getter and setters`
}
For a one-to-one conversion from Student to StudentInfo:
class StudentInfo {
public static StudentInfo of(Student student) {
StudentInfo si = new StudentInfo();
si.setAddressNo(student.getAddressNo());
si.setCode(student.getCode());
si.setTax(student.getTax());
return si;
}
}
To convert from one Map to the other:
Map<Long,List<Student>> studentMap = ...
Map<Long,List<StudentInfo>> studentInfoMap = studentMap.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, //same key
entry -> entry.getValue().stream()
.map(StudentInfo::of) //conversion of Student to StudentInfo
.collect(Collectors.toList()) //or simply `.toList()` as of Java 16
));
Now your grouping....
From the JavaDoc for java.util.stream.Stream<T> public abstract <R, A> R collect(java.util.stream.Collector<? super T, A, R> collector):
The following will classify Person objects by city:
Map<String, List<Person>> peopleByCity
= personStream.collect(Collectors.groupingBy(Person::getCity));
The following will classify Person objects by state and city, cascading two Collectors together:
Map<String, Map<String, List<Person>>> peopleByStateAndCity
= personStream.collect(Collectors.groupingBy(Person::getState,
Collectors.groupingBy(Person::getCity)));
Note how the last example produces a Map with another Map as its values.
Now, summingDouble over StudentInfo::getTax produces a BigDecimal, not a Map. Replacing with groupingBy will work to classify Students that have the same amount for getTax:
Map<String, Map<Long, Map<BigDecimal, List<StudentInfo>>>> summary =
studentInfoMap.values().stream()
.flatMap(List::stream) //YOU ALSO NEED THIS
.collect(
Collectors.groupingBy(StudentInfo::getCode,
Collectors.groupingBy(StudentInfo::getAddressNo,
Collectors.groupingBy(StudentInfo::getTax)))
);
Edit: Retaining the 1,2,3,4 original keys
To retain the original keys you can iterate or stream the original entrySet, which contains both key and value:
Map<Long,Map<String, Map<Long, Map<BigDecimal, List<StudentInfo>>>>> summaryWithKeys =
studentInfoMap.entrySet().stream() //NOTE streaming the entrySet not just values
.collect(
Collectors.toMap(Map.Entry::getKey, //Original Key with toMap
entry -> entry.getValue().stream() //group the value-List members
.collect(Collectors.groupingBy(StudentInfo::getCode,
Collectors.groupingBy(StudentInfo::getAddressNo,
Collectors.groupingBy(StudentInfo::getTax))))
));
Just as an exercise, if you want a flat map (Map<MyKey,List>) you need a composite key MyKey
As per my comment, if you are looking to have a single flat Map, you could design a composite key, which would need to implement both equals() and hashCode() to contract. For example, this is what Lombok would generate for StudentInfo (yes, its easier to depend on lombok and use #EqualsAndHashCode):
public boolean equals(final Object o) {
if(o == this) return true;
if(!(o instanceof StudentInfo)) return false;
final StudentInfo other = (StudentInfo) o;
if(!other.canEqual((Object) this)) return false;
if(this.getAddressNo() != other.getAddressNo()) return false;
final Object this$code = this.getCode();
final Object other$code = other.getCode();
if(this$code == null ? other$code != null : !this$code.equals(other$code)) return false;
final Object this$tax = this.getTax();
final Object other$tax = other.getTax();
if(this$tax == null ? other$tax != null : !this$tax.equals(other$tax)) return false;
return true;
}
protected boolean canEqual(final Object other) {return other instanceof StudentInfo;}
public int hashCode() {
final int PRIME = 59;
int result = 1;
final long $addressNo = this.getAddressNo();
result = result * PRIME + (int) ($addressNo >>> 32 ^ $addressNo);
final Object $code = this.getCode();
result = result * PRIME + ($code == null ? 43 : $code.hashCode());
final Object $tax = this.getTax();
result = result * PRIME + ($tax == null ? 43 : $tax.hashCode());
return result;
}
You might then use StudentInfo as the composite key as follows:
Map<Long, List<Student>> studentMap = ...
Map<StudentInfo,List<Student>>> summaryMap = studentMap.values().stream()
.collect(Collectors.groupingBy(StudentInfo::of))
));
This means that you now have a nested map referenced by the composite key. Students that have exactly the same addressNo, code and tax will be part of the List referenced by each such key.
Edit: Retaining original keys
Similarly, if you wanted to retain the original keys, you could either add them into the composite key, or similar as above:
Map<Long, List<Student>> studentMap = ...
Map<Long, Map<StudentInfo,List<Student>>>> summaryMap = studentMap.entrySet().stream()
.collect(Collectors.groupingBy(Map.Entry::getKey,
Collectors.groupingBy(StudentInfo::of)))
));
Map<Integer, Object> map = new HashMap<>();
map.put(1, studentInfoMap.values().stream().map(
student -> student.getAddressNo()
).collect(Collectors.toList()));
map.put(2, studentInfoMap.values().stream().map(
student -> student.getCode()
).collect(Collectors.toList()));
// and so ...
To convert the map from Student to StudentInfo, whilst keeping the same keys, you could do something like this:
Set<Long> keys = studentMap.keySet();
List<Long> keylist = new ArrayList<>(keys);
Map<Long, List<StudentInfo>> studentInfoMap = new HashMap<>();
for(int i = 0; i < keys.size(); i++){
long key = keylist.get(i);
List<Student> list = studentMap.get(key);
List<StudentInfo> result = new ArrayList<>();
// Create the new StudentInfo object with your values
list.forEach(s -> result.add(new StudentInfo(s.name())));
// Put them into the new Map with the same keys
studentInfoMap.put(key, result);
}

How to convert enum with attribute to map using java8 stream

this answer doesn't math: How to store enum to map using Java 8 stream API
I have an enum:
public enum SheetRows{
totalActive("Total active");
String value;
SheetRows(String value){
this.value = value;
}
public String getValueForTable() {
return value;
}
}
How to convert this enum to HashMap<SheetRows, String>?
I try to use:
HashMap<SheetRows, String> cellsMap = Arrays.asList(SheetRows.values()).stream()
.collect(Collectors.toMap(k -> k, v -> v.getValueForTable()));
but this code isn't compile.
The following should work, the problem is that you're trying to assign a Map to HashMap
Map<SheetRows, String> map = EnumSet.allOf(SheetRows.class)
.stream()
.collect(Collectors.toMap(Function.identity(), SheetRows::getValueForTable));
System.out.println("map = " + map); // map = {totalActive=Total active}
If you really need to return a HashMap, you can also use the overload that takes a supplier and a mergeFunction like hereunder.
The mergeFunction will never be called since your enums are unique, so just choose a random one.
HashMap<SheetRows, String> map = EnumSet.allOf(SheetRows.class)
.stream()
.collect(Collectors.toMap(Function.identity(), SheetRows::getValueForTable, (o1, o2) -> o1, HashMap::new));
System.out.println("map = " + map); // map = {totalActive=Total active}

How to generalize a method to sort Map Entries in Java?

In my code, I've got a few maps. Then I have a method like the one below, which takes one of the maps, sorts the entries by their value and returns a list of the top ones, with the amount given by the parameter.
Example:
if the input map is like
"a" = 5,
"b" = 4,
"c" = 8,
"d" = 0,
and I call the method with quantity = 2 in the parameter, I'm given a list of the 2 highest map entries, sorted decreasingly
"c" = 8,
"a" = 5.
Right now, I have one such method for each of the maps and they only differ in:
the <Type1, Type2> declarations all over the method, and
the (distances.entrySet()); population of the all list.
Can I generalize this somehow to have just one alike method, being able to receive any of the types?
private static Map<String, Double> distances = new TreeMap<>();
private static Map<String, Integer> titles = new TreeMap<>();
private static Map<Integer, Integer> hours = new TreeMap<>();
private static Map<Date, Integer> days = new TreeMap<>();
public static List<Entry<String, Double>> getTopDistances(int quantity) {
List<Map.Entry<String, Double>> all = new ArrayList<>(distances.entrySet());
List<Map.Entry<String, Double>> requested = new ArrayList<>();
Collections.sort(all, new Comparator<Map.Entry<String, Double>>() {
#Override
public int compare(Entry<String, Double> e1, Entry<String, Double> e2) {
return (e2.getValue().compareTo(e1.getValue()));
}
});
int i = 0;
while (all.iterator().hasNext() && ++i <= quantity) {
requested.add(all.get(i - 1));
}
return requested;
}
I can surely continue with all the methods separated, but I sense a better way of doing it. Have researched generics, wildcards, collections and interfaces, which I think is the way to go, yet I still need a push.
With Java Streams one single line can produce what you want from the map to the final list. Bellow I package it in a private method which is a bit more neat but you could inline it if you want.
Since all your values are Comparable:
private <K, V extends Comparable<V>> List<Map.Entry<K,V>> getTop(Map<K,V> map, int quantity) {
return map.entrySet().stream()
.sorted((a,b) -> b.getValue().compareTo(a.getValue()))
.limit(quantity)
.collect(Collectors.toList());
}
If the value type was not comparable you then would need to pass the comparator as an additional parameter:
private <K, V> List<Map.Entry<K,V>> getTop(Map<K,V> map, Comparator<V> cmp, int quantity) {
return map.entrySet().stream()
.sorted((a,b) -> cmp.compare(b,a))
.limit(quantity)
.collect(Collectors.toList());
}
You cannot create comparator who will compare two comparables with different types, so you have to do this like this:
private static Map<String, Double> distances = new TreeMap<>();
private static Map<String, Integer> titles = new TreeMap<>();
private static Map<Integer, Integer> hours = new TreeMap<>();
private static Map<Date, Integer> days = new TreeMap<>();
public static List<Map.Entry<String, Double>> getTopDistances(int quantity) {
List<Map.Entry<String, Double>> all = new ArrayList<>(distances.entrySet());
List<Map.Entry<String, Double>> requested = new ArrayList<>();
all.sort(naturalOrder());
int i = 0;
while (all.iterator().hasNext() && ++i <= quantity) {
requested.add(all.get(i - 1));
}
return requested;
}
public static <T extends Comparable<? super T>> Comparator<Map.Entry<?,T> naturalOrder() {
return (e1, e2) -> e2.getValue().compareTo(e2.getValue());
}

java iterator in a nested map (Map 2D)

I would like to know: how can I iterate through a 2D map? I have a centralMap:
private final Map<String, Map<String, String>> centralMap =
new HashMap<String, Map<String, String>>();
which contains another map:
Map<String,String> nestedMap = new HashMap<String, String>();
as Value, and the second one is created in the "put" method, so that in the constructor, i have just the centralMap. Now i want to redefine this method and to get complete entry of the map (the 2 keys and the value for each element)
public Iterator<Entry> iterator()
How should I proceed? If possible, how can I remove an element through the iterator without having a problem?
Iterators are meant to operate on a collection, such as the keyset of your first map, or the values (which are a collection of maps) of your nested one. You cannot expect the iterator renove method to understand your complex structure.
I would suggest that you build your own class for this, with your own convenience methods that do what you described.
Also, going on a limb here: make sure you didn't just want to have a multimap. If so, have a look, for example, at guava's HashMultimap
You apply the same procedure as if you were iterating over a single map, you just do it twice:
public void printNestedMap(Map<String, Map<String, String>> map)
Iterator it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pairs = (Map.Entry)it.next(); // <- pairs.getValue() is a map
System.out.println("Key1: " + pairs.getKey());
//print the inner Map
printMap((Map<String, String>)pairs.getValue());
it.remove(); // avoids a ConcurrentModificationException
}
}
EDIT
It would actually be better to move the iteration over a single map to a different method to be called in this scenario.
public void printMap(Map<String, String>> map)
{
Iterator it = map.entrySet().iterator();
while(it.hasNext())
{
Map.Entry pairs = (Map.Entry)it.next(); // <- pairs.getValue() is a String
System.out.println("Key2: " + pairs.getKey() + " Value2: " + pairs.getValue());
it.remove();
}
}
EDIT 2: Test Program
import java.util.*;
public class TestMap
{
public static void main(String[] args)
{
Map<String, String> innerMap = new HashMap<>();
Map<String, Map<String, String>> outerMap = new HashMap<>();
innerMap.put("Key1", "Val1");
innerMap.put("Key2", "Val2");
innerMap.put("Key3", "Val3");
innerMap.put("Key4", "Val4");
outerMap.put("OuterKey1", innerMap);
printNestedMap(outerMap);
}
public static void printNestedMap(Map<String, Map<String, String>> map)
{
Iterator it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pairs = (Map.Entry)it.next(); // <- pairs.getValue() is a map
System.out.println("Key1: " + pairs.getKey());
//print the inner Map
printMap((Map<String, String>)pairs.getValue());
it.remove(); // avoids a ConcurrentModificationException
}
}
public static void printMap(Map<String, String> map)
{
Iterator it = map.entrySet().iterator();
while(it.hasNext())
{
Map.Entry pairs = (Map.Entry)it.next(); // <- pairs.getValue() is a String
System.out.println("Key2: " + pairs.getKey() + " Value2: " + pairs.getValue());
it.remove();
}
}
}
Output:
Key1: OuterKey1
Key2: Key2 Value2: Val2
Key2: Key1 Value2: Val1
Key2: Key4 Value2: Val4
Key2: Key3 Value2: Val3
If you want to get Map.Entry elements containing the two keys and the value, it will really be much more natural to create a class Pair<String, String> that combines the two keys in a single element and use that as the key in a single map rather than nesting maps.
If you do this, your main structure will be a Map<Pair<String, String>, String> and using the Map.entrySet() method will give you a Set<Map.Entry<String, String>, String> from which you can get an iterator that gives approximately what you're after.
If you need to have a Map<String, Map<String, String>> for other reasons, it is also possible to convert this into the structure described above by reasonably simple code, and this might be the most sensible way of getting the information out of it.
Edit Note:
The Pair class described above is essentially the same as Map.Entry, so you could avoid creating a new class for the key by building a Map<Map.Entry<String, String>, String>. I think it makes the code a bit less clear, but it can certainly be made functionally equivalent.
Sample Code
In the code below, I have defined the Pair class as an inner static (for real use, you might want to extract as an independent class), and written a conversion that takes a nested map as you describe, converts it to the form I've suggested, and uses an iterator on the entries of the converted map to print the values.
The iterator could of course be used for other things, and the convert method and Pair class are generic.
import java.util.*;
public class TestMap
{
public static void main(String[] args)
{
Map<String, String> innerMap1 = new HashMap<String, String>();
Map<String, String> innerMap2 = new HashMap<String, String>();
Map<String, Map<String, String>> outerMap = new HashMap<String, Map<String, String>>();
innerMap1.put("InnerKey1", "Val1");
innerMap1.put("InnerKey2", "Val2");
innerMap1.put("InnerKey3", "Val3");
innerMap1.put("InnerKey4", "Val4");
innerMap2.put("InnerKey5", "Val5");
innerMap2.put("InnerKey6", "Val6");
innerMap2.put("InnerKey7", "Val7");
innerMap2.put("InnerKey8", "Val8");
outerMap.put("OuterKey1", innerMap1);
outerMap.put("OuterKey2", innerMap2);
Map<Pair<String, String>, String> convertedMap = convert(outerMap);
for (Map.Entry<Pair<String, String>, String> entry: convertedMap.entrySet()) {
System.out.println(String.format("OuterKey: %s, InnerKey: %s, Value: %s",
entry.getKey().getFirst(),
entry.getKey().getSecond(),
entry.getValue()
));
}
}
private static <K1,K2,V> Map<Pair<K1, K2>,V> convert(Map<K1, Map<K2,V>> nestedMap) {
Map<Pair<K1, K2>, V> result = new HashMap<Pair<K1, K2>, V>();
for (Map.Entry<K1, Map<K2, V>> outerEntry: nestedMap.entrySet()) {
final K1 outerKey = outerEntry.getKey();
for (Map.Entry<K2, V> innerEntry: outerEntry.getValue().entrySet()) {
final K2 innerKey = innerEntry.getKey();
final V value = innerEntry.getValue();
result.put(new Pair<K1, K2>(outerKey, innerKey), value);
}
}
return result;
}
public static class Pair<T1, T2> {
private T1 first;
private T2 second;
public Pair(T1 first, T2 second) {
this.first = first;
this.second = second;
}
public T1 getFirst() {
return first;
}
public T2 getSecond() {
return second;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Pair pair = (Pair) o;
if (first != null ? !first.equals(pair.first) : pair.first != null) return false;
if (second != null ? !second.equals(pair.second) : pair.second != null) return false;
return true;
}
#Override
public int hashCode() {
int result = first != null ? first.hashCode() : 0;
result = 31 * result + (second != null ? second.hashCode() : 0);
return result;
}
}
}
Note on Usage in context:
In your current code, you have a class with a field centralMap which is the map in your old nested form, and an integer counter for the size of the map.
This containing class has a method for adding entries that looks like this:
#Override
public String put(final String row, final String column, final String value) {
/**
* Second map which is contained by centralMap, that contain Strings as Keys
* and Values.
*/
Map<String, String> nestedMap;
if (centralMap.containsKey(row))
nestedMap = centralMap.get(row);
else
nestedMap = new HashMap<String, String>();
if (!nestedMap.containsKey(column))
counter++;
centralMap.put(row, nestedMap);
return nestedMap.put(column, value);
}
If instead of using the nested map at all, you change this field to a map of the suggested form, this method would become a bit simpler:
#Override
public String put(final String row, final String column, final String value) {
Pair<String, String> key = new Pair(row, column);
if (centralMap.contains(key)
counter++;
centralMap.put(key, value);
}
And you actually wouldn't need the counter anymore, as it will always contain the same value as centralMap.size().
Update:
From edits put in yesterday but now deleted, it's now clear to me (from edit history) that you want to build a single iterator that delegates to all the iterators of the map in correct sequence, and returns a simple structure containing both keys and the value.
This is certainly possible, and if I have time later, I might add some sample code for it. As was noted in another response, the iterator.remove() method may be impossible or unnatural.
Meanwhile, your requirements (as noted a comment on the same other response) is rather similar to what is supplied by guava's Table. That's open source, and looking at it may give you ideas. You can download the source for guava here.
Specifically, in guava's StandardTable, there is an inner class CellIterator, which looks like:
private class CellIterator implements Iterator<Cell<R, C, V>> {
final Iterator<Entry<R, Map<C, V>>> rowIterator
= backingMap.entrySet().iterator();
Entry<R, Map<C, V>> rowEntry;
Iterator<Entry<C, V>> columnIterator
= Iterators.emptyModifiableIterator();
#Override public boolean hasNext() {
return rowIterator.hasNext() || columnIterator.hasNext();
}
#Override public Cell<R, C, V> next() {
if (!columnIterator.hasNext()) {
rowEntry = rowIterator.next();
columnIterator = rowEntry.getValue().entrySet().iterator();
}
Entry<C, V> columnEntry = columnIterator.next();
return Tables.immutableCell(
rowEntry.getKey(), columnEntry.getKey(), columnEntry.getValue());
}
#Override public void remove() {
columnIterator.remove();
if (rowEntry.getValue().isEmpty()) {
rowIterator.remove();
}
}
}
You can't just copy this code as it depends on other things in guava, but it shows the basic pattern of what you have to do.

Java invert map

I need create inverse map - select unique values and for them find keys.
Seems that only way is to iterate all key/value pairs, because entrySet returns set of <key,value> so value not unique?
The values in a map may not be unique. But if they are (in your case) you can do as you wrote in your question and create a generic method to convert it:
private static <V, K> Map<V, K> invert(Map<K, V> map) {
Map<V, K> inv = new HashMap<V, K>();
for (Entry<K, V> entry : map.entrySet())
inv.put(entry.getValue(), entry.getKey());
return inv;
}
Java 8:
public static <V, K> Map<V, K> invert(Map<K, V> map) {
return map.entrySet()
.stream()
.collect(Collectors.toMap(Entry::getValue, Entry::getKey));
}
Example of usage:
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("Hello", 0);
map.put("World!", 1);
Map<Integer, String> inv = invert(map);
System.out.println(inv); // outputs something like "{0=Hello, 1=World!}"
}
Side note: the put(.., ..) method will return the the "old" value for a key. If it is not null you may throw a new IllegalArgumentException("Map values must be unique") or something like that.
Take a look at Google Guava BiMap.
Example usage
Map<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
Map<String, Integer> inverted = HashBiMap.create(map).inverse();
To get an inverted form of a given map in java 8:
public static <K, V> Map<V, K> inverseMap(Map<K, V> sourceMap) {
return sourceMap.entrySet().stream().collect(
Collectors.toMap(Entry::getValue, Entry::getKey,
(a, b) -> a) //if sourceMap has duplicate values, keep only first
);
}
Example usage
Map<Integer, String> map = new HashMap<Integer, String>();
map.put(1, "one");
map.put(2, "two");
Map<String, Integer> inverted = inverseMap(map);
Seems that only way is to iterate all key/value pairs, because entrySet returns set of so value not unique?
It's one way at least. Here's an example:
Map<Integer, String> map = new HashMap<Integer, String>();
map.put(1, "one");
map.put(2, "two");
Map<String, Integer> inverted = new HashMap<String, Integer>();
for (Integer i : map.keySet())
inverted.put(map.get(i), i);
In case of non-unique values, this algorithm will map the last value found to it's key. (Since the iteration order is undefined for most maps, this should be as good as any solution.)
If you really do want to keep the first value found for each key, you could change it to
if (!inverted.containsKey(map.get(i)))
inverted.put(map.get(i), i);
I would give another approach to this problem giving an extra dimension:
duplicate values in EntrySet.
public static void main(String[] args) {
HashMap<Integer, String> s = new HashMap<Integer, String>();
s.put(1, "Value1");
s.put(2, "Value2");
s.put(3, "Value2");
s.put(4, "Value1");
/*
* swap goes here
*/
HashMap<String,List<Integer>> newMap = new HashMap<String, List<Integer>>();
for (Map.Entry<Integer, String> en : s.entrySet()) {
System.out.println(en.getKey() + " " + en.getValue());
if(newMap.containsKey(en.getValue())){
newMap.get(en.getValue()).add(en.getKey());
} else {
List<Integer> tmpList = new ArrayList<Integer>();
tmpList.add(en.getKey());
newMap.put(en.getValue(), tmpList);
}
}
for(Map.Entry<String, List<Integer>> entry: newMap.entrySet()){
System.out.println(entry.getKey() + " " + entry.getValue());
}
}
T result will be that:
1 Value1 2 Value2 3 Value2 4 Value1 Value1 [1, 4] Value2 [2, 3]
Apache Commons Collections also provides a BidiMap interface for bi-directional maps, along with several implementations.
BidiMap JavaDoc
If your values duplicate and you need to store keys in list you can go with
val invertedMap = originalMap.entrySet().stream()
.collect(Collectors.groupingBy(
Map.Entry::getValue,
Collectors.mapping(Map.Entry::getKey, Collectors.toList()))
);
You have to assume that values may be identical, since the Map contract allows it.
In my opinion the best solution lies in using a wrapper. It will contain the original value, and add an id. Its hashCode() function will rely on the id, and you provide a Getter for the original value.
Code would be something like this:
public class MapKey
{
/**
* A new ID to differentiate equal values
*/
private int _id;
/**
* The original value now used as key
*/
private String _originalValue;
public MapKey(String originalValue)
{
_originalValue = originalValue;
//assuming some method for generating ids...
_id = getNextId();
}
public String getOriginalValue()
{
return _originalValue;
}
#Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + _id;
return result;
}
#Override
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MapKey other = (MapKey) obj;
if (_id != other._id)
return false;
return true;
}
#Override
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("MapKey value is ");
sb.append(_originalValue);
sb.append(" with ID number ");
sb.append(_id);
return sb.toString();
}
Inverting the map would be something like this:
public Map <MapKey, Integer> invertMap(Map <Integer, String> map)
{
Map <MapKey, Integer> invertedMap = new HashMap <MapKey, Integer>();
Iterator<Entry<Integer, String>> it = map.entrySet().iterator();
while(it.hasNext())
{
//getting the old values (to be reversed)
Entry<Integer, String> entry = it.next();
Integer oldKey = entry.getKey();
String oldValue = entry.getValue();
//creating the new MapKey
MapKey newMapKey = new MapKey(oldValue);
invertedMap.put(newMapKey, oldKey);
}
return invertedMap;
}
Printing the values something like this:
for(MapKey key : invertedMap.keySet())
{
System.out.println(key.toString() + " has a new value of " + invertedMap.get(key));
}
None of this code is tested, but I believe it's the best solution since it makes use of OO inheritance design instead of "c" style checks and allows you to display all the original keys and values.
With Guava
Multimaps.transformValues(Multimaps.index(map.entrySet(), Map.Entry::getValue),
Map.Entry::getKey)
You'll get a multimap (basically a map of lists) in return.

Categories