Reversing key value pairs in a HashMap - java

I have a data structure as follows:
Map<String,ArrayList<String>> graph = new HashMap<String,ArrayList<String>>();
This is essentially a hash map which puts string values as keys and stores array list of strings in the value for the keys.
Now I am trying to reverse the key value pattern to make value the key and key the value. The way I am doing it is as follows:
private Map<String,ArrayList<String>> reverseAdjList(Map<String,ArrayList<String>> adjList){
Map<String,ArrayList<String>> tGraph = new HashMap<String,ArrayList<String>>();
for (Map.Entry<String, ArrayList<String>> entry : adjList.entrySet()) {
String key = entry.getKey();
ArrayList<String> values = new ArrayList<>();
values.add(key);
ArrayList<String> value = entry.getValue();
for(String v:value){
if(tGraph.containsKey(v)){
values.addAll(tGraph.get(v));
}
tGraph.put(v, values);
}
}
return tGraph;
}
So this works for me in reversing the hash map keys values pattern for small data set however when I try it on a larger dataset I run into
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3210)
at java.util.Arrays.copyOf(Arrays.java:3181)
at java.util.ArrayList.grow(ArrayList.java:261)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
at java.util.ArrayList.addAll(ArrayList.java:579)
at GraphProcessor.reverseAdjList(GraphProcessor.java:67)
at GraphProcessor.SCC(GraphProcessor.java:135)
at GraphProcessor.<init>(GraphProcessor.java:50)
at GraphProcessor.main(GraphProcessor.java:250)
I know this is a very naïve and wrong approach to do it, what is a better and correct way to do it?

There's a bug in your code:
for (Map.Entry<String, ArrayList<String>> entry : adjList.entrySet()) {
String key = entry.getKey();
ArrayList<String> values = new ArrayList<>(); // Wrong place for this variable.
values.add(key);
ArrayList<String> value = entry.getValue();
for(String v:value){
if(tGraph.containsKey(v)){
values.addAll(tGraph.get(v));
}
tGraph.put(v, values);
}
}
The local variable values should be in the nested for loop, otherwise values are accumulated for all later new key v and will cost a lot of memory if your dataset is large, it should be:
private Map<String, ArrayList<String>> reverseAdjList(Map<String, List<String>> adjList) {
Map<String, ArrayList<String>> tGraph = new HashMap<>();
for (Map.Entry<String, List<String>> entry : adjList.entrySet()) {
String key = entry.getKey();
List<String> value = entry.getValue();
for (String v : value) {
ArrayList<String> values = new ArrayList<>();
values.add(key);
if (tGraph.containsKey(v)) {
values.addAll(tGraph.get(v));
}
tGraph.put(v, values);
}
}
return tGraph;
}
But actually you don't need to create a new List instance for each inner for step, try the following code with JDK 1.8:
private Map<String, List<String>> reverseMap(Map<String, List<String>> adjList) {
Map<String, List<String>> tGraph = new HashMap<>();
for (Map.Entry<String, List<String>> entry : adjList.entrySet()) {
for (String value : entry.getValue()) {
tGraph.computeIfAbsent(value, v -> new ArrayList<>()).add(entry.getKey()); // Updated according comment from #shmosel
}
}
return tGraph;
}
If you're using older version of jdk, you can try:
private Map<String, List<String>> reverseMap(Map<String, List<String>> adjList) {
Map<String, List<String>> tGraph = new HashMap<>();
for (Map.Entry<String, List<String>> entry : adjList.entrySet()) {
for (String value : entry.getValue()) {
List<String> newValues = tGraph.get(value);
if (newValues == null) {
newValues = new ArrayList<>();
tGraph.put(value, newValues);
}
newValues.add(entry.getKey());
}
}
return tGraph;
}
Hope this could be helpful :-)

Related

Hashmap inside a hashmap not removing the keys using remove?

This is my list.
list = Stream.of(
"06|20|1",
"11|20|2",
"11|20|2",
"07|207|6",
"11|207|2",
"07|207|6",
).collect(Collectors.toList());
I have a hashmap such as:
HashMap<String, HashMap<String, String>> hashMap = new HashMap<>();
HashMap<String, String> newHash = new HashMap<>();
And my code is
for (String line : list) {
String key, value, priority;
key = line.split("\\|", -1)[1];
value = line.split("\\|", -1)[0];
priority = line.split("\\|", -1)[2];
if (hashMap.containsKey(key)) {
HashMap<String, String> getPriority = hashMap.get(key);
Map.Entry<String, String> entry = getPriority.entrySet().iterator().next();
String oldKey = entry.getKey();
String previousPrior = getPriority.get(oldKey);
if (Integer.parseInt(priority) > Integer.parseInt(previousPrior)) {
getPriority.remove(oldKey);
getPriority.put(value,priority);
hashMap.put(key, getPriority);
}
} else {
newHash.put(value, priority);
System.out.println(newhas);
hashMap.put(key, newhas);
}
}
I want to have the have the key with highest priority only such as:
{20={11=2},207={07=6}}
as 11 and 7 has the highest valaues in 20 and 207.
But i am getting all values in the inner hashmap.
How about using streams instead?
Map<String, Map<String, String>> map = list.stream()
.map(line -> line.split("\\|"))
.sorted(Comparator.comparingInt(line -> Integer.parseInt(line[2])))
.collect(Collectors.toMap(
line -> line[1],
line -> Map.of(line[0], line[2]),
(low, high) -> high));
Ideone Demo
For each new key that you insert in your Map variable in the else condition of your code you need to create new HashMap to insert it along with the new key.
What you are doing is using the same newhas variable for all the keys in your Map variable.
So change this
else {
newhas.put(value, priority);
System.out.println(newhas);
Map.put(key, newhas);
}
to
else {
newHas = new HashMap<>();
newhas.put(value, priority);
System.out.println(newhas);
Map.put(key, newhas);
}
Ideone Demo

Collection.shuffle is not working on Map keys nor on the Map values. I have a map within a map. I want to shuffle the map that is the innermost

for second entry of idAndTags the inner-map is shuffled but not for the first
FIRST APPROACH!
for (Map.Entry<String, Map<String, String>> entryOne : idAndTags.entrySet()) {
List keys = new ArrayList(entryOne.getValue().keySet());
System.out.println(keys);
Collections.shuffle(keys);
System.out.println(keys);
}
SECOND APPROACH!
for (Map.Entry<String, Map<String, String>> entryOne : idAndTags.entrySet()) {
shufTags = new HashMap<String, String>();
Map<String, String> tags = entryOne.getValue();
System.out.println(tags);
final List<String> vs = new ArrayList<String>(tags.values());
Collections.shuffle(vs);
final Iterator<String> vIter = vs.iterator();
for (String k : tags.keySet())
shufTags.put(k, vIter.next());
System.out.println(shufTags);
}
Map#.keySet() returns a view of the key set, which doesn't support ordering. Shuffling it has no effect on the map.
You can not change the order of iteration of a HashMap, but a LinkedHashMap iterates itself in the order of insertion, so you can replace the inner maps with a LinkedHashMap and use a shuffled key set to drive the insertion.
didn't have a choice, solved it like this... seems to work for me!
Map<String, Map<String, String>> _idAndTagsShuffled = new HashMap<String, Map<String, String>>();
for (Map.Entry<String, Map<String, String>> entryOne : _idAndTags.entrySet()) {
ArrayList<String> keys = new ArrayList(entryOne.getValue().keySet());
shuffledMap = new HashMap<String, String>();
do {
shuffled = new ArrayList<String>();
Collections.shuffle(keys);
for (String key : keys) {
shuffled.add(key);
}
int count = 0;
isShuffled = false;
for (String tag : entryOne.getValue().keySet()) {
if (!tag.equals(shuffled.get(count))) {
isShuffled = true;
}
count++;
}
} while (!isShuffled);
_idAndTagsShuffled.put(entryOne.getKey(), shuffledMap);
}

Hashtable Key and value storing in separate array list

java code
Hashtable<String, Integer> gettingStatus = searchActiveAssetStatus();
ArrayList<String> statusValue = new ArrayList<String>();
ArrayList<String> statusCount = new ArrayList<String>();
Set<String> keys = gettingStatus.keySet();
for (String key : keys) {
if (key.contains("Active")) {
statusValue.add(key);
}
if (key.contains("Abandoned")) {
statusValue.add(key);
}
if (key.contains("Deleted")) {
statusValue.add(key);
}
}
for the above i have stored Key in an separate array list (available), at the same time if that key exist in hashtable i want to store the value for that corresponding key in another array list (statusCount) how?
You may use the entrySet() method instead of the keySet() method:
Set<Entry<String, Integer>> entries = gettingStatus.entrySet();
for (Entry<String, Integer> entry : entries) {
String key = entry.getKey();
Integer value = entry.getValue();
if (key.contains("Active")) {
statusValue.add(key);
// Store value
}
if (key.contains("Abandoned")) {
statusValue.add(key);
// Store value
}
if (key.contains("Deleted")) {
statusValue.add(key);
// Store value
}
}
If I got your question right you just have to ask for the value of the corresponding key and put it in the list:
Hashtable<String, Integer> gettingStatus = searchActiveAssetStatus();
ArrayList<String> statusValue = new ArrayList<String>();
ArrayList<String> statusCount = new ArrayList<String>();
Set<String> keys = gettingStatus.keySet();
for (String key : keys) {
if (key.contains("Active") || key.contains("Abandoned") || key.contains("Deleted")) {
statusValue.add(key);
statusCount.add(gettingStatus.get(key).toString());
}
}
Or as Benjamin said just take entrySet() which makes it even easier.

What's the best way to sum two Map<String,String>?

I have the following maps.
Map<String,String> map1= new HashMap<String, String>(){{
put("no1","123"); put("no2","5434"); put("no5","234");}};
Map<String,String> map1 = new HashMap<String, String>(){{
put("no1","523"); put("no2","234"); put("no3","234");}};
sum(map1, map2);
I want to join them to one, summing up similar keyed values together. What;s the best way I could do it using java 7 or guava libraries ?
expected output
Map<String, String> output = { { "no1" ,"646"}, { "no2", "5668"}, {"no5","234"}, {"no3","234" } }
private static Map<String, String> sum(Map<String, String> map1, Map<String, String> map2) {
Map<String, String> result = new HashMap<String, String>();
result.putAll(map1);
for (String key : map2.keySet()) {
String value = result.get(key);
if (value != null) {
Integer newValue = Integer.valueOf(value) + Integer.valueOf(map2.get(key));
result.put(key, newValue.toString());
} else {
result.put(key, map2.get(key));
}
}
return result;
}
try this
Map<String, List<String>> map3 = new HashMap<String, List<String>>();
for (Entry<String, String> e : map1.entrySet()) {
List<String> list = new ArrayList<String>();
list.add(e.getValue());
String v2 = map2.remove(e.getKey());
if (v2 != null) {
list.add(v2);
}
map3.put(e.getKey(), list);
}
for (Entry<String, String> e : map2.entrySet()) {
map3.put(e.getKey(), new ArrayList<String>(Arrays.asList(e.getValue())));
}
Java 8 introduces Map.merge(K, V, BiFunction), which makes this easy if not particularly concise:
Map<String, String> result = new HashMap<>(map1);
//or just merge into map1 if mutating it is okay
map2.forEach((k, v) -> result.merge(k, v, (a, b) ->
Integer.toString(Integer.parseInt(a) + Integer.parseInt(b))));
If you're doing this repeatedly, you're going to be parsing and creating a lot of strings. If you're generating the maps one at a time, you're best off maintaining a list of strings and only parsing and summing once.
Map<String, List<String>> deferredSum = new HashMap<>();
//for each map
mapN.forEach((k, v) ->
deferredSum.computeIfAbsent(k, x -> new ArrayList<String>()).add(v));
//when you're done
Map<String, String> result = new HashMap<>();
deferredSum.forEach((k, v) -> result.put(k,
Long.toString(v.stream().mapToInt(Integer::parseInt).sum())));
If this summing is a common operation, consider whether using Integer as your value type makes more sense; you can use Integer::sum as the merge function in that case, and maintaining lists of deferred sums would no longer be necessary.
Try this
Map<String,String> map1= new HashMap<String, String>(){{
put("no1","123"); put("no2","5434"); put("no5","234");}};
Map<String,String> map2 = new HashMap<String, String>(){{
put("no1","523"); put("no2","234"); put("no3","234");}};
Map<String,String> newMap=map1;
for(String a:map2.keySet()){
if(newMap.keySet().contains(a)){
newMap.put(a,""+(Integer.parseInt(newMap.get(a))+Integer.parseInt(map2.get(a))));
}
else{
newMap.put(a,map2.get(a));
}
}
for(String k : newMap.keySet()){
System.out.println("key : "+ k + " value : " + newMap.get(k));
}

Inverted HashMap in Java whit frequency

I have a HashMap<String, LinkedList<Integer>> and I want to create a hashMapInverted<LinkedList<Integer>, Set<String>> which contains in keys the lists, values of first map, and the values the set of strings which have the same list in first map.
How can I do this?
You can do:
Map<LinkedList<Integer>, Set<String>> mapInverted = new HashMap<>(myMap.size());
for(Entry<<String, LinkedList<Integer>> entry : myMap.entrySet()) {
String key = entry.getKey();
LinkedList<Integer> list = entry.getValue();
Set<String> strings = mapInverted.get(list);
if(strings == null) { // the list has not already been put in the map
strings = new HashSet<String>(); // create a new set
mapInverted.put(list, strings); // put the list and the new set
}
strings.add(key);
}
You can try -
Map<String, LinkedList<Integer>> map =new HashMap<String, LinkedList<Integer>>();
Map<LinkedList<Integer>, Set<String>> invertMap = new HashMap<LinkedList<Integer>, Set<String>>();
for (Entry<String, LinkedList<Integer>> entry : map.entrySet()) {
if(invertMap.containsKey(entry.getValue())){
invertMap.get(entry.getValue()).add(entry.getKey());
}else{
Set<String> set = new HashSet<String>();
set.add(entry.getKey());
invertMap.put(entry.getValue(), set);
}
}

Categories

Resources