map.getOrDefault().add() in Java not works - java

The traditional code works well like below:
Map<Integer, List<Integer>> map = new HashMap<>();
if (!map.containsKey(1)) {
map.put(1, new ArrayList<>());
}
map.get(1).add(2);
Now I'd like to try the magic of getOrDefault:
map.getOrDefault(1, new ArrayList<>()).add(2);
But if I use the above line, then map.get(1) is null.
Why?

Because getOrDefault, as its name suggests, only gets stuff from the map. It doesn't adds a new KVP to the map. When the key is not present, the default value you pass to getOrDefault is returned, but not added to the map, so you'd be adding 2 to an array list that is thrown away immediately.
In other words, this is what your getOrDefault code is doing:
ArrayList<Integer> value;
if (!map.containsKey(1)) {
value = new ArrayList<>();
} else {
value = map.get(1);
}
value.add(2);
You should use computeIfAbsent instead. This method actually adds the return value from the function to the map if the key is not present:
map.computeIfAbsent(1, x -> new ArrayList<>()).add(2);

or you could do:
if(!map.contansKey(1)) map.put(1, new ArrayList<>());
map.get(1).add(2);
so you can save those lines ;)

Related

Java HashMap key fits to a pattern?

I have a Map dataset, and I want to iterate through the keys and search for matches.
So I want to find the maps element, where the key fits to this pattern:
String searchedKey = "A?C"; // ? means it can be any character
Map<String, MyObject> myMap = new HashMap<>();
myMap.put("ABC", MyObject(1));
myMap.put("CDF", MyObject(2));
myMap.put("ADS", MyObject(3));
for (Map.Entry<String,MyObject> entry : myMap.entrySet()) {
// in this case, I want to find the first element, because it's key fits the searchedKey, where ? can be anything
}
How can I do this?
Thanks!
You could do something like this to return a list of found MyObjects. Note I changed ? to . for any character.
String searchedKey = "A.C"; // ? means it can be any character
Map<String, MyObject> myMap = new HashMap<>();
myMap.put("ABC", new MyObject(1));
myMap.put("CDF", new MyObject(2));
myMap.put("ARS", new MyObject(3));
myMap.put("VS", new MyObject(4));
myMap.put("AQC", new MyObject(3));
myMap.put("DS", new MyObject(3));
myMap.put("ASC", new MyObject(10));
List<Map.Entry<String,MyObject>> list = myMap.entrySet().stream()
.filter(e -> e.getKey().matches(searchedKey))
.collect(Collectors.toList());
list.forEach(System.out::println);
Prints
ASC=10
ABC=1
AQC=3
The MyObject class
class MyObject {
int val;
public MyObject(int v) {
this.val = v;
}
public String toString() {
return val + "";
}
}
You could use Regex-Patterns that allow to search Strings for matchings of a logical sequence using String#matches(String).
Here is a page that might help you create and test a regex for your needs. You might also have to construct your pattern flexible during runtime, depending on how your search works.
Tho keep in mind that a HashMap does not keep the order in which the keys were inserted. keySet() does not return them in a fixed order. If you need them ordered, you could use a LinkedHashMap

How to put together multiple Maps <Character, Set<String>> without overriding Sets

In my project I am using two maps Map<Character, Set<String>>.
map1 - is temporally holding needed values
map2 - is summing all data from map1 after each loop
for example i got:
map2 = (B; Beryllium, Boron, Bromine)
map2 = (H; Hellum, Hydrogen, Hafnium)
now new map1 is:
map1 = (B; Bismuth)
map1 = (O; Oxygen)
In my code adding Oxygen as new entry is ok, but adding new entry for B ends by overraidding existing data in values and leave me only Bismuth.
My code:
while (iterator.hasNext()) {
Set<String> words = new TreeSet<>();
String word = iterator.next();
char[] wordChars = word.toCharArray();
//some code
words.add(word);
map1.put(wordChars[i], words);
}
map2.putAll(map1);
I tought about using .merge but I have no idea how to use it with Sets as values, and I cannot use simple Strings with concat.
You can use Map#merge like this:
Map<String, Set<String>> map1; // [key="B";values=["Beryllium", "Boron", "Bromine"]]
Map<String, Set<String>> map2; // [key="B";values=["Bismuth"] key="I";values=["Iron"]]
for (Entry<String, Set<String>> entry : map2.entrySet()) {
map1.merge(entry.getKey(), entry.getValue(), (s1, s2) -> {s1.addAll(s2); return s1;});
}
//map1 = [key="B";values=["Beryllium", "Boron", "Bromine", "Bismuth"] key="I";values=["Iron"]]
Map::compute is probably what you're looking for. This gives you a way to map any existing value (if there is one), or provide one if not.
For example, in your case something like the following would probably suffice:
oldMap.compute("B", current -> {
if (current == null) {
// No existing entry, so use newMap's one
return newMap.get("B");
} else {
// There was an existing value, so combine the Sets
final Set<String> newValue = new HashSet<>(current);
newValue.addAll(newMap.get("B"));
return newValue;
}
});
There's also MultiValueMap and Multimap from spring and guava respectively (if you're ok bringing in dependencies) which cover this case with less work already.
Temporary map1 will not be needed in this case. Get the set for that character, if null create a new set. Add the word to that set and put in the map:
while (iterator.hasNext()) {
String word = iterator.next();
//some code
Set<String> words = map2.get(word.charAt(0));
if(words == null) {
words = new TreeSet<>();
}
words.add(word);
map2.put(word.charAt(0), words);
}
When using the merge() function, if the specified key is not already associated with a value or the value is null, it associates the key with the given value.
Otherwise, i.e if the key is associated with a value, it replaces the value with the results of the given remapping function. So in order to do not overwrite the old value you must write your remapping function so that it combines the old and new values.
To do so replace this line :
map2.putAll(map1);
with
map1.forEach( (key, value)->{
map2.merge(key, value, (value1,value2) -> Stream.of(value1,value2)
.flatMap(Set::stream)
.collect(Collectors.toSet()));
});
This will iterate over map1 and add echh key which is not present into map2 and associate it with the given value and for each key which is already present it combines the old values and new values.
Alternative you can also work with Map.computeIfPresent and Map.putIfAbsent
map1.forEach( (key, value)->{
map2.computeIfPresent(key, (k,v) -> Stream.of(v,value).flatMap(Set::stream).collect(Collectors.toSet()));
map2.putIfAbsent(key, value);
});

How to get or create new map entry

I'm trying to write a simple utility function to get a value from a Map and, if it's not found to create a new value class and put that in the map.
It seems though very difficult to get the classes of the map's key and value at runtime and the best I can come up with is something horrible along the following lines.
Is there a better way?
private Object getOrCreate( Map<Object, Object> map, Object key, Class<?> mapValueClass ) {
Object value = map.get( key );
if (value == null) {
Constructor<?> con = mapValueClass.getConstructor( key.getClass() );
value = con.newInstance( key );
map.put( key, value );
}
return value;
}
You should ckeck out Map::getOrDefault and Map::computeIfAbsent (added in Java 8); those do pretty much exactly what your function is supposed to do. The difference between the two is that getOrDefault will accept an existing instance (created before the method is invoked) and return it if needed, but will not add it to the map, while computeIfAbsend accepts a function for lazily creating a new value, and will also add that value to the map.
Map<String, List<Integer>> map = new HashMap<>();
List<Integer> list1 = map.getOrDefault("foo", Collections.emptyList());
System.out.println(list1); // empty list
System.out.println(map); // map is still empty
List<Integer> list2 = map.computeIfAbsent("bar", s -> new ArrayList<Integer>());
System.out.println(list2); // empty list
System.out.println(map); // entry added to map
Assuming that you always want to create a new instance of the Value class with the key as parameter, and assuming that that class actually has such a constructor) you could e.g. use this:
YourClass obj = map.computeIfAbsent(key, YourClass::new);

In Java8 functional style, how can i map the values to already existing key value pair

I have a map:
Map<String, List<Object>> dataMap;
Now i want to add new key value pairs to the map like below:
if(dataMap.contains(key)) {
List<Object> list = dataMap.get(key);
list.add(someNewObject);
dataMap.put(key, list);
} else {
List<Object> list = new ArrayList();
list.add(someNewObject)
dataMap.put(key, list);
}
How can i do this with Java8 functional style?
You can use computeIfAbsent.
If the mapping is not present, just create one by associating the key with a new empty list, and then add the value into it.
dataMap.computeIfAbsent(key, k -> new ArrayList<>()).add(someNewObject);
As the documentation states, it returns the current (existing or computed) value associated with the specified key so you can chain the call with ArrayList#add. Of course this assume that the values in the original map are not fixed-size lists (I don't know how you filled it)...
By the way, if you have access to the original data source, I would grab the stream from it and use Collectors.groupingBy directly.
This can be simplified by using the ternary operator. You don't really need the if-else statement
List<Object> list = dataMap.containsKey(key) ? dataMap.get(key) : new ArrayList<>();
list.add(someNewObject);
dataMap.put(key, list);
You can also use compute method.
dataMap.compute(key, (k, v) -> {
if(v == null)
return new ArrayList<>();
else {
v.add(someNewObject);
return v;
}
});
you can use
dataMap.compute(key,(k,v)->v!=null?v:new ArrayList<>()).add(someNewObject)
or
dataMap.merge(key,new ArrayList<>(),(v1,v2)->v1!=null?v1:v2).add(someNewObject)

Hashmap value getting updated in all keys

I am having map like this
HashMap<String, List<String>> map = new HashMap<String, List<String>>();
map---> {A=[], B=[], C=[]}
I am trying to add "hai" to key A.
But it is getting added to all key. Below is my code
I am wrong somewhere
for (Entry<String, List<String>> entry : map.entrySet()) {
String a = entry.getKey();
if(a.equals(attr)){
List<String> temp = entry.getValue();
temp.add("hai");
map.put(a, temp);
System.out.println("----------"+map);
}
}
output: ------------{A=[hai], B=[hai], C=[hai]}
please suggest
Thanks in advance
Not sure why that's happening, maybe as Eran suggested it's in code you aren't showing. However, there's a much easier way to do this instead of iterating through all the keys...
Map<String, List<String>> map = new HashMap<>();
...
List<String> values = map.get(attr);
if(values == null) {
values = new ArrayList<String>();
map.put(attr, values);
}
values.add("hai");
And I'm just guessing here, but I suspect you are doing this to create the array in the first place...
Map<String, List<String>> map = new HashMap<>();
List<String> values = new ArrayList<>();
map.put("A", values);
map.put("B", values);
map.put("C", values);
This causes A, B, and C to all share the same instance of the List. Therefore when you manipulate the list under one key (say, A), you are really making the same change to the lists stored under all keys, because it is the SAME list for all three.
The fix for that is described above, but essentially you want to create a new instance of List for each key in the map.
You are probably putting the same List in all the values of the Map. However, that happens in code you didn't show. When you put a key in the Map for the first time, make sure you are creating a new List for its value.

Categories

Resources