Java: How to get set of keys having same value in hashmap - java

I have a hashmap as below:
1->x
2->y
3->x
4->z
Now i want to know all keys whose value is x (ans: [1,3] ). what is best way to do?
Brute force way is to just iterate over map and store all keys in array whose value is x.
Is there any efficient way for this.
Thanks

A hashmap is a structure that is optimized for associative access of the values using the keys, but is in no way better in doing the reverse then an array for instance. I don't think you can do any better then just iterate. Only way to improve efficiency is if you have a reverse hash map as well(i.e. hash map where you hold an array of keys pointing to a given value for all values).

You can use a MultiMap to easily get all those duplicate values.
Map<Integer, String> map = new HashMap<Integer, String>();
map.put(1, "x");
map.put(2, "y");
map.put(2, "z");
map.put(3, "x");
map.put(4, "y");
map.put(5, "z");
map.put(6, "x");
map.put(7, "y");
System.out.println("Original map: " + map);
Multimap<String, Integer> multiMap = HashMultimap.create();
for (Entry<Integer, String> entry : map.entrySet()) {
multiMap.put(entry.getValue(), entry.getKey());
}
System.out.println();
for (Entry<String, Collection<Integer>> entry : multiMap.asMap().entrySet()) {
System.out.println("Original value: " + entry.getKey() + " was mapped to keys: "
+ entry.getValue());
}
Prints out:
Original map: {1=x, 2=z, 3=x, 4=y, 5=z, 6=x, 7=y}
Original value: z was mapped to keys: [2, 5]
Original value: y was mapped to keys: [4, 7]
Original value: x was mapped to keys: [1, 3, 6]
Per #noahz's suggestion, forMap and invertFrom takes fewer lines, but is arguably more complex to read:
HashMultimap<String, Integer> multiMap =
Multimaps.invertFrom(Multimaps.forMap(map),
HashMultimap.<String, Integer> create());
in place of:
Multimap<String, Integer> multiMap = HashMultimap.create();
for (Entry<Integer, String> entry : map.entrySet()) {
multiMap.put(entry.getValue(), entry.getKey());
}

If Java 8 is an option, you could try a streaming approach:
Map<Integer, String> map = new HashMap<>();
map.put(1, "x");
map.put(2, "y");
map.put(3, "x");
map.put(4, "z");
Map<String, ArrayList<Integer>> reverseMap = new HashMap<>(
map.entrySet().stream()
.collect(Collectors.groupingBy(Map.Entry::getValue)).values().stream()
.collect(Collectors.toMap(
item -> item.get(0).getValue(),
item -> new ArrayList<>(
item.stream()
.map(Map.Entry::getKey)
.collect(Collectors.toList())
))
));
System.out.println(reverseMap);
Which results in:
{x=[1, 3], y=[2], z=[4]}
If Java 7 is preferred:
Map<String, ArrayList<Integer>> reverseMap = new HashMap<>();
for (Map.Entry<Integer,String> entry : map.entrySet()) {
if (!reverseMap.containsKey(entry.getValue())) {
reverseMap.put(entry.getValue(), new ArrayList<>());
}
ArrayList<Integer> keys = reverseMap.get(entry.getValue());
keys.add(entry.getKey());
reverseMap.put(entry.getValue(), keys);
}
As an interesting aside, I experimented with the time required for each algorithm when executing large maps of (index,random('a'-'z') pairs.
10,000,000 20,000,000
Java 7: 615 ms 11624 ms
Java 8: 1579 ms 2176 ms

If you are open to using a library, use Google Guava's Multimaps utilities, specifically forMap() combined with invertFrom()

Yup, just brute force. You can make it fast by also storing a Multimap from Value -> Collection of Key, at the expense of memory and runtime cost for updates.

HashMap computes the hashcode() of the key, not of the values. Unless you store some kind of additional information, or consider using a different data structure, I think the only way you can get this is brute force.
If you need to perform efficient operation on the values, you should think whether you're using the appropriate data structure.

If you are using a hashmap there is no efficient way doing it but iterating the values

If you already have a map, you should consider using Google's Guava library to filter the entries you're interested in. You can do something along the lines of:
final Map<Integer, Character> filtered = Maps.filterValues(unfiltered, new Predicate<Character>() {
#Override
public boolean apply(Character ch) {
return ch == 'x';
}
});

I agree with George Campbell but for java 8 I would do it a bit easier:
Map<String, List<Integer>> reverseMap = map.entrySet()
.stream()
.collect(Collectors.groupingBy(Map.Entry::getValue,
Collectors.mapping(
Map.Entry::getKey,
Collectors.toList())));

Try This.....
public static void main(String[] args) {
HashMap<String, String> hashMap = new HashMap<String, String>();
hashMap.put("cust_tenure", "3_sigma");
hashMap.put("cust_age", "3_sigma");
hashMap.put("cust_amb_6m_sav", "3_sigma");
hashMap.put("cust_amb_6m_chq", "3_sigma");
hashMap.put("cust_total_prod_6m", "3_sigma");
HashMap<String, ArrayList<String>> result = new LinkedHashMap<String, ArrayList<String>>();
for (String key : hashMap.keySet()) {
ArrayList<String> colName = null;
if (!result.containsKey(hashMap.get(key))) {
colName = new ArrayList<String>();
colName.add(key);
result.put(hashMap.get(key), colName);
} else {
colName = result.get(hashMap.get(key));
colName.add(key);
result.put(hashMap.get(key), colName);
}
System.out.println(key + "\t" + hashMap.get(key));
}
for (String key : result.keySet()) {
System.out.println(key + "\t" + result.get(key));
}
System.out.println(hashMap.size());
}

Related

Invert Map | Values ---> Keys

I want to be able to inverse a given HashMap that has multiple keys can point to the same value.
HashMap<String, String> cities = new HashMap<String, String>();
cities.put("Manchester", "UK");
cities.put("London", "UK");
static HashMap<String, String> inverseMap(HashMap map) {
// something that has "UK" point to both "Manchester" and "London"
// if it can be done without using any special Java 8 feature, that would be great
}
I am unsure where to start.
Do it like this. Basically, it employs a merge function which concatenates values for a duplicate key.
Create a new map
Use the values of the old map for the keys to the new
If the new map does not have a value for the key, put the value in the new map
Otherwise, concatenate the value to the old value for that key
HashMap<String, String> cities = new HashMap<String, String>();
cities.put("Manchester", "UK");
cities.put("London", "UK");
cities.put("New York", "US");
cities.put("Chicago", "US");
Map<String,String> inverted = new HashMap<>();
for (String key : cities.keySet()) {
String newKey = cities.get(key);
String value = inverted.get(newKey);
if (value == null) {
inverted.put(newKey, key);
} else {
value = value + ", " + key;
inverted.put(newKey, value);
}
}
for (Entry<String,String> e : inverted.entrySet()) {
System.out.println(e.getKey() + " -> " + e.getValue());
}
It prints
UK -> Manchester, London
US -> New York, Chicago
Since you didn't specify how to handle duplicate keys. I could also have stored it in a Map<String,List<String>>
Is this what you're looking for?
Map<String, String> cities = new HashMap<>();
cities.put("Manchester", "UK");
cities.put("London", "UK");
Map<String, List<String>> reverseMap = new HashMap<>();
for (Entry<String, String> entry : cities.entrySet()) {
List<String> list = reverseMap.get(entry.getValue());
if (list == null) {
list = new ArrayList<>();
reverseMap.put(entry.getValue(), list);
}
list.add(entry.getKey());
}
System.out.println(reverseMap);
As multiple keys can contain the same value, you will have to be able to store multiple values per key in the inversed Map. I recommend using a Set as a value for this.
Create a map that can save a Set of Strings as values
Iterate through the original map
If the country is not found in the new map, create an entry there
Add the city to the map
This should also work with Java 7 without dependencies.
static HashMap<String, Set<String>> inverseMap(HashMap<String,String> map) {
HashMap<String,Set<String>> inversed=new HashMap<>();
for(Map.Entry<String,String> entry:map.entrySet()){
if(!inversed.containsKey(entry.getValue())){
inversed.put(entry.getValue(),new HashSet<>());
}
inversed.get(entry.getValue()).add(entry.getKey());
}
return inversed;
}
{Manchester=UK,London=UK} would turn into {UK={Manchester,London}} (order may differ).
You can look at MultiMap. It allows mapping of a single key to multiple values.
This is something has been implemented in both Google Guava
https://guava.dev/releases/19.0/api/docs/com/google/common/collect/Multimap.html
ListMultimap<String, String> multimap = ArrayListMultimap.create();
for (President pres : US_PRESIDENTS_IN_ORDER) {
multimap.put(pres.firstName(), pres.lastName());
}
for (String firstName : multimap.keySet()) {
List<String> lastNames = multimap.get(firstName);
out.println(firstName + ": " + lastNames);
}
... produces output such as:
Zachary: [Taylor]
John: [Adams, Adams, Tyler, Kennedy] // Remember, Quincy!
George: [Washington, Bush, Bush]
Grover: [Cleveland, Cleveland] // Two, non-consecutive terms, rep'ing NJ!
As in Apache Commons Collections
https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/MultiValuedMap.html
MultiValuedMap<K, String> map = new MultiValuedHashMap<K, String>();
map.put(key, "A");
map.put(key, "B");
map.put(key, "C");
Collection<String> coll = map.get(key);
coll will be a collection containing "A", "B", "C".
You can iterate over the entry set of your original map and use the values (country code) as key and add each key (cities) to a list:
static HashMap<String, List<String>> inverseMap(HashMap<String, String> map) {
HashMap<String, List<String>> countryToCity = new HashMap<>();
for(Map.Entry<String,String> entry: map.entrySet()){
countryToCity.computeIfAbsent(entry.getValue(), k -> new ArrayList<>()).add(entry.getKey());
}
return countryToCity;
}

Comparing two hash-maps and printing intersections

I have two hash maps: one contains an Integer key and String value.
The other contains an Integer key and float value.
Code
Map<Integer,String> mapA = new HashMap<>();
mapA.put(1, "AS");
mapA.put(2, "Wf");
Map<Integer,Float> mapB = new HashMap<>();
mapB.put(2, 5.0f);
mapB.put(3, 9.0f);
My question is how to compare the two hash maps using the integer key value? I want to print the bitmap value when the key values are the same.
You can just iterate on the keys of mapA and check if it is present in mapB then add the value to a third mapC for example.
Map<String, float> mapC = new HashMap<String, float>();
for (Integer key : mapA.keySet()) {
if (mapB.containsKey(key)) {
mapC.put(mapA.get(key), mapB.get(key));
}
}
Compare keys in two map by using mapB iterator.
Iterator<Entry<Integer, Float>> iterator = mapB.entrySet().iterator();
while(iterator.hasNext()) {
Entry<Integer, Float> entry = iterator.next();
Integer integer = entry.getKey();
if(mapA.containsKey(integer)) {
System.out.println("Float Value : " + entry.getValue());
}
}
If you are allowed to modify mapB, then the solution is as simple as mapB.keySet().retainAll(mapA.keySet());.
This will only leave those entries in mapB that have a corresponding key in mapA, because the set returned by keySet() is backed by the map itself, any changes made to it will be reflected to the map.
yes i got solution...
if(mapB.containsKey(position)){
Log.e("bucky",mapB.get(position));}
position means integer value.
With Java 8 Streams API:
Map<Integer, Object> matchInBothMaps = mapA
.entrySet()
.stream()
.filter(map -> mapB.containsKey(map.getKey()))
.collect(Collectors.toMap(map -> map.getKey(),
map -> map.getValue()));
System.out.println(matchInBothMaps);

What is the best practices to merge two maps

How can I add a new map to existing map. The maps have the same type Map<String, Integer>. If the key from new map exists in the old map the values should be added.
Map<String, Integer> oldMap = new TreeMap<>();
Map<String, Integer> newMap = new TreeMap<>();
//Data added
//Now what is the best way to iterate these maps to add the values from both?
By add, I assume you want to add the integer values, not create a Map<String, List<Integer>>.
Before java 7, you'll have to iterate as #laune showed (+1 to him). Otherwise with java 8, there is a merge method on Map. So you could do it like this:
Map<String, Integer> oldMap = new TreeMap<>();
Map<String, Integer> newMap = new TreeMap<>();
oldMap.put("1", 10);
oldMap.put("2", 5);
newMap.put("1", 7);
oldMap.forEach((k, v) -> newMap.merge(k, v, (a, b) -> a + b));
System.out.println(newMap); //{1=17, 2=5}
What it does is that for each key-value pair, it merges the key (if it's not yet in newMap, it simply creates a new key-value pair, otherwise it updates the previous value hold by the existing key by adding the two Integers)
Also maybe you should consider using a Map<String, Long> to avoid overflow when adding two integers.
for( Map.Entry<String,Integer> entry: newMap.entrySet() ) {
// get key and value from newMap and insert/add to oldMap
Integer oldVal = oldMap.get( entry.getKey() );
if( oldVal == null ){
oldVal = entry.getValue();
} else {
oldVal += entry.getValue();
}
newMap.put( entry.getKey(), oldVal );
}
Hope that this is what you meant

How to putAll on Java hashMap contents of one to another, but not replace existing keys and values?

I need to copy all keys and values from one A HashMap onto another one B, but not to replace existing keys and values.
Whats the best way to do that?
I was thinking instead iterating the keySet and checkig if it exist or not, I would
Map temp = new HashMap(); // generic later
temp.putAll(Amap);
A.clear();
A.putAll(Bmap);
A.putAll(temp);
It looks like you are willing to create a temporary Map, so I'd do it like this:
Map tmp = new HashMap(patch);
tmp.keySet().removeAll(target.keySet());
target.putAll(tmp);
Here, patch is the map that you are adding to the target map.
Thanks to Louis Wasserman, here's a version that takes advantage of the new methods in Java 8:
patch.forEach(target::putIfAbsent);
Using Guava's Maps class' utility methods to compute the difference of 2 maps you can do it in a single line, with a method signature which makes it more clear what you are trying to accomplish:
public static void main(final String[] args) {
// Create some maps
final Map<Integer, String> map1 = new HashMap<Integer, String>();
map1.put(1, "Hello");
map1.put(2, "There");
final Map<Integer, String> map2 = new HashMap<Integer, String>();
map2.put(2, "There");
map2.put(3, "is");
map2.put(4, "a");
map2.put(5, "bird");
// Add everything in map1 not in map2 to map2
map2.putAll(Maps.difference(map1, map2).entriesOnlyOnLeft());
}
Just iterate and add:
for(Map.Entry e : a.entrySet())
if(!b.containsKey(e.getKey())
b.put(e.getKey(), e.getValue());
Edit to add:
If you can make changes to a, you can also do:
a.putAll(b)
and a will have exactly what you need. (all the entries in b and all the entries in a that aren't in b)
You can make it in just 1 line if you change maps order in #erickson's solution:
mapWithNotSoImportantValues.putAll( mapWithImportantValues );
In this case you replace values in mapWithNotSoImportantValues with value from mapWithImportantValues with the same keys.
Java 8 solution using Map#merge
As of java-8 you can use Map#merge(K key, V value, BiFunction remappingFunction) which merges a value into the Map using remappingFunction in case the key is already found in the Map you want to put the pair into.
// using lambda
newMap.forEach((key, value) -> map.merge(key, value, (oldValue, newValue) -> oldValue));
// using for-loop
for (Map.Entry<Integer, String> entry: newMap.entrySet()) {
map.merge(entry.getKey(), entry.getValue(), (oldValue, newValue) -> oldValue);
}
The code iterates the newMap entries (key and value) and each one is merged into map through the method merge. The remappingFunction is triggered in case of duplicated key and in that case it says that the former (original) oldValue value will be used and not rewritten.
With this solution, you don't need a temporary Map.
Let's have an example of merging newMap entries into map and keeping the original values in case of the duplicated antry.
Map<Integer, String> newMap = new HashMap<>();
newMap.put(2, "EVIL VALUE"); // this will NOT be merged into
newMap.put(4, "four"); // this WILL be merged into
newMap.put(5, "five"); // this WILL be merged into
Map<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
newMap.forEach((k, v) -> map.merge(k, v, (oldValue, newValue) -> oldValue));
map.forEach((k, v) -> System.out.println(k + " " + v));
1 one
2 two
3 three
4 four
5 five
public class MyMap {
public static void main(String[] args) {
Map<String, String> map1 = new HashMap<String, String>();
map1.put("key1", "value1");
map1.put("key2", "value2");
map1.put("key3", "value3");
map1.put(null, null);
Map<String, String> map2 = new HashMap<String, String>();
map2.put("key4", "value4");
map2.put("key5", "value5");
map2.put("key6", "value6");
map2.put("key3", "replaced-value-of-key3-in-map2");
// used only if map1 can be changes/updates with the same keys present in map2.
map1.putAll(map2);
// use below if you are not supposed to modify the map1.
for (Map.Entry e : map2.entrySet())
if (!map1.containsKey(e.getKey()))
map1.put(e.getKey().toString(), e.getValue().toString());
System.out.println(map1);
}}
With Java 8 there is this API method to accomplish your requirement.
map.putIfAbsent(key, value)
If the specified key is not already associated with a value (or is mapped to null) associates it with the given value and returns null, else returns the current value.
As others have said, you can use putIfAbsent. Iterate over each entry in the map that you want to insert, and invoke this method on the original map:
mapToInsert.forEach(originalMap::putIfAbsent);

How does one convert a HashMap to a List in Java?

In Java, how does one get the values of a HashMap returned as a List?
HashMap<Integer, String> map = new HashMap<Integer, String>();
map.put (1, "Mark");
map.put (2, "Tarryn");
List<String> list = new ArrayList<String>(map.values());
for (String s : list) {
System.out.println(s);
}
Assuming you have:
HashMap<Key, Value> map; // Assigned or populated somehow.
For a list of values:
List<Value> values = new ArrayList<Value>(map.values());
For a list of keys:
List<Key> keys = new ArrayList<Key>(map.keySet());
Note that the order of the keys and values will be unreliable with a HashMap; use a LinkedHashMap if you need to preserve one-to-one correspondence of key and value positions in their respective lists.
Basically you should not mess the question with answer, because it is confusing.
Then you could specify what convert mean and pick one of this solution
List<Integer> keyList = Collections.list(Collections.enumeration(map.keySet()));
List<String> valueList = Collections.list(Collections.enumeration(map.values()));
Collection Interface has 3 views
keySet
values
entrySet
Other have answered to to convert Hashmap into two lists of key and value. Its perfectly correct
My addition: How to convert "key-value pair" (aka entrySet)into list.
Map m=new HashMap();
m.put(3, "dev2");
m.put(4, "dev3");
List<Entry> entryList = new ArrayList<Entry>(m.entrySet());
for (Entry s : entryList) {
System.out.println(s);
}
ArrayList has this constructor.
Solution using Java 8 and Stream Api:
private static <K, V> List<V> createListFromMapEntries (Map<K, V> map){
return map.values().stream().collect(Collectors.toList());
}
Usage:
public static void main (String[] args)
{
Map<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
map.put(3, "three");
List<String> result = createListFromMapEntries(map);
result.forEach(System.out :: println);
}
If you only want it to iterate over your HashMap, no need for a list:
HashMap<Integer, String> map = new HashMap<Integer, String>();
map.put (1, "Mark");
map.put (2, "Tarryn");
for (String s : map.values()) {
System.out.println(s);
}
Of course, if you want to modify your map structurally (i.e. more than only changing the value for an existing key) while iterating, then you better use the "copy to ArrayList" method, since otherwise you'll get a ConcurrentModificationException. Or export as an array:
HashMap<Integer, String> map = new HashMap<Integer, String>();
map.put (1, "Mark");
map.put (2, "Tarryn");
for (String s : map.values().toArray(new String[]{})) {
System.out.println(s);
}
If you wanna maintain the same order in your list, say:
your Map looks like:
map.put(1, "msg1")
map.put(2, "msg2")
map.put(3, "msg3")
and you want your list looks like
["msg1", "msg2", "msg3"] // same order as the map
you will have to iterate through the Map:
// sort your map based on key, otherwise you will get IndexOutofBoundException
Map<String, String> treeMap = new TreeMap<String, String>(map)
List<String> list = new List<String>();
for (treeMap.Entry<Integer, String> entry : treeMap.entrySet()) {
list.add(entry.getKey(), entry.getValue());
}
I use usually map.values() to get values, then convert them to list
let say you have this Hashmap:
HashMap<String, Integer> map = new HashMap<>();
map.put("one", 1);
map.put("two", 2);
map.put("three", 3);
You can get values from the map, then convert them to a list in one code line like that:
List<Integer> values = map.values().stream()
.collect(Collectors.toList());

Categories