I have this snippet of code, and I want to use forEach after computeIfpresent function. Mainly, if the key is found, then we should loop over the values (list), and fetch each entry and add it to another list. Any idea how I can do that?
List<Long> myArrayList = new ArrayList();
Map<Long, Set<Long>> myMap = new HashMap();
Set<Long> mySet = MyMap().get(id);
if (mySet != null)
{
for (Long ex : mySet)
{
myArrayList.add(ex);
}
}
-->??
myMap.computeIfPresent(id, (key, value) -> value.forEach(ex -> myArrayList.add(ex)));
computeIfPresent is for changing the value inside the HashMap under the given key (id in your case). It means that such operation is not allowed because by running forEach on the key:value pair you are not providing any new mapping value for a map
What you could do would be
myMap.computeIfPresent("a", (k, v) -> {
((Set<Long>)v).forEach(e -> myArrayList.add(e));
return v;
});
but it looks bad and is violating the purpose of computeIfPresent method
You should just use rather traditional approach
if (myMap.containsKey(id)) {
myArrayList.addAll(myMap.get(id));
}
or use an Optional
Optional.ofNullable(map.get(id)).ifPresent(set ->
myArrayList.addAll((Set<Long>)set)
);
...but is this more readable? :)
If I get it right, you are looking for Map.getOrDefault instead of Map.computeIfPresent chained with a foreach. Using Map.getOrDefault the task could be rewritten to:
List<Long> myArrayList = new ArrayList<>();
Map<Long, Set<Long>> myMap = new HashMap<>();
long id = ...;
myArrayList.addAll(myMap.getOrDefault(id, Collections.emptySet()));
Related
I am struggling to convert an existing forloop to a lambda expression.
I have a list of objects (List<Task>) which contains a map called 'InputData' (Map<String, String>).
I also have another Map<String, String> (Acceptance).
I want to filter the list of Task objects where the Task.InputData Map contains all the 'Acceptance' map entries. Task.InputData may contain additional entries but I want to enforce the vars that exist in Acceptance must exist in Task.InputData.
The existing for loop looks like so:
boolean addTask;
List<Task> finalResults = new ArrayList<>();
for (Task t : results) {
addTask = true;
for (Entry<String, String> k : kvVars.entrySet()) {
if (!t.getInputData().containsKey(k) || !t.getInputData().get(k).equals(k.getValue())) {
addTask = false;
}
}
if (addTask) {
finalResults.add(t);
}
}
I'm a bit confused about an approach, whether I should be trying to combine flatmap and filter criteria or if I should follow the logic in the existing for loop.
You can use the filter to collect all Task's that having all the 'Acceptance' k/v in InputData
List<Task> finalResults = results.stream()
.filter(t->kvVars.entrySet()
.stream()
.allMatch(k->t.getInputData().containsKey(k.getKey())
&& t.getInputData().get(k.getKey()).equals(k.getValue()))
.collect(Collectors.toList());
You can use Set.containsAll on the entry set of the input data map to check your condition:
List<Task> finalResults = results.stream()
.filter(t -> t.getInputData().entrySet().containsAll(kvVars.entrySet()))
.collect(Collectors.toList());
I have function which returns a Map<String, Set<String>>, Code before java 8:
Map<String, Set<String>> degreeMap = new HashMap<>();
for(Course course : courses){
Set<String> cList = degreeMap.get(course.getCourseLevel().toString());
if(Objects.nonNull(cList)){
cList.addAll(course.getMasterDegree()); //this is what i want to append to the old set
degreeMap.put(course.getCourseLevel().toString(), cList);
} else{
degreeMap.put(course.getCourseLevel().toString(), new HashSet<>(course.getMasterDegree()));
}
}
return degreeMap;
Which return a map of courselevel -> set of degrees.
For example, it read all the courses and return a map like:
{"undergraduate" : ["BTech", "BSc", "BE"],
"masters": ["MTech", "MBA"],
"Executive": ["PGDBM", "EECP"]}
Here is my Course class:
public class Course {
private List<String> masterDegree;
private CourseLevel courseLevel;
}
But I want to write this piece of code in Java 8 style. For that, I tried this:
Map<String, Set<String>> degreeMap = courses.stream().collect(
Collectors.groupingBy(c -> c.getCourseLevel().toString(),
Collectors.mapping(c -> c.getMasterDegree(), Collectors.toSet()))
);
which is not working and I am getting the following compile-time error on this:
no instance(s) of type variable(s) exist so that List conforms to String inference variable T has incompatible bounds: equality constraints: String lower bounds: List
Any suggestion, how to achieve this?
Not tested, but looks like, you're looking for something like:
return courses.stream()
.collect(Collectors.toMap(course -> course.getCourseLevel().toString(),
course -> new HashSet<>(course.getMasterDegree()),
(set1, set2) -> Stream.of(set1, set2)
.flatMap(Set::stream).collect(Collectors.toSet())));
You might be interested in the Collectors.toMap() method.
Here is an example that you may need to tweak as I did not test it.
Map<String, Set<String>> degreeMap = courses.stream()
.collect(
Collectors.toMap(
item -> item.getCourseLevel().toString(), //your key mapping
item -> item.getMasterDegree(), //your value mapping
(oldValue, newValue) -> { //your process of aggregation
if (null == oldValue) {
return newValue;
} else {
oldValue.addAll(newValue);
return oldValue;
}
},
LinkedHashMap::new //your result initialiser
)
);
Also, another tip: you do not need to get by key and check for null, you can use the .compute(), .computeIfAbsent(), .computeIfPresent() methods on the map
I don't think you need lamda expression, you should refactor your codes to make it clear instead.
// supposed to be initialied with non-empty values
// Map<String, Set<String>> degreeMap = ...
for(Course course : courses){
// something like below
String key = course.getCourseLevel().toString();
// degreeSet should not be null
Set<String> degreeSet = course.getMasterDegree();
// TODO: check nullable
degreeSet.putAll(degreeMap.get(key));
degreeMap.put(key, degreeSet);
}
return degreeMap;
Map<String,Integer> map=new HashMap<String,Integer>();
map.put("A",1);
map.put("A",2);
map.put("A",3);
map.put("B",4);
Here My key is A and it will override previous value of A and give value of key A is 3. But I want to store all the values of this key like i want to store 1 ,2 and 3.Then please tell me how all these value of particular key is stored in arraylist.
That doesn’t work in this way. Map keys are unique by definition.
You will need a
Map<String, List<Integer>>
Of course before you add a key you need to lookup if an entry already exists. If not, add a new Arraylist using the key, and add the value to the new list.
Or a much mature alternative could be Guava's multiMap.
You can find the reference to it's usage here
Hope it helps!
Try this and hope it helps.
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class MapwithDupKeys {
public static void main(String[] args) {
Map<String, List<Integer>> myMultiMap = new HashMap<>();
add(myMultiMap, "A", 1);
add(myMultiMap, "A", 2);
add(myMultiMap, "A", 3);
add(myMultiMap, "B", 4);
System.out.println(myMultiMap);
}
static void add(Map<String, List<Integer>> map, String key, Integer value) {
if (map.get(key) == null) {
List valueList = new ArrayList();
valueList.add(value);
map.put(key, valueList);
} else
((ArrayList) map.get(key)).add(value);
}
}
Lets analyze the requirement
You have a key of type String which is needed to map with a collection(unique) of values of type Integer. (unique is my assumption). I mean ("xyz", 1) and ("xyz,1) in case of these two entries in the map it has to be seen as only one entry.
From point 1 we can define a structure for an entry : [ Key- String , Value- Set ]
A map is needed to hold entries of type as mentioned in point 2.
We can have a map like below.
HashMap <String, Set<Integer>>
Lets translate it to easiest implementation, although there may be other options too.
private Map<String, Set<Integer>> map = new HashMap<>();
public void putPair( String key, Integer value){
Set<Integer> values = map.get(key);
if(values == null){
values = new HashSet<Integer>();
map.put(key, values);
}
values.add(value);
}
In case multiple same values also you want you can use simple ArrayList instead of Set. But this case better way is to encapsulate the Integer in another wrapper class and keep a count. increment the count in case of same entry.
As per your requirements, you don't need a Map<String, Integer>, but a Map<String, List<Integer>> instead. In other words, you're after a multimap.
One way to achieve such data structure in Java 8+, is by using the Map.computeIfAbsent and Map.computeIfPresent methods for insertions and removals, respectively:
Map<String, List<Integer>> map = new HashMap<>(); // use diamond operator
// INSERT
map.computeIfAbsent("A", k -> new ArrayList<>()).add(1);
map.computeIfAbsent("A", k -> new ArrayList<>()).add(2);
map.computeIfAbsent("A", k -> new ArrayList<>()).add(3);
map.computeIfAbsent("B", k -> new ArrayList<>()).add(4);
// REMOVE
map.computeIfPresent("A", (k, v) -> {
v.remove(1);
return v.isEmpty() ? null : v;
});
map.computeIfPresent("A", (k, v) -> {
v.remove(2);
return v.isEmpty() ? null : v;
});
map.computeIfPresent("A", (k, v) -> {
v.remove(3);
return v.isEmpty() ? null : v;
});
map.computeIfPresent("B", (k, v) -> {
v.remove(4);
return v.isEmpty() ? null : v;
});
EDIT:
The remapping function argument for the removals could be extarcted out to the following utility method:
static <K, V> BiFunction<K, List<V>> removing(V elem) {
return (k, v) -> { v.remove(elem); return v.isEmpty() ? null : v; };
}
Which could then be used as follows:
map.computeIfPresent("A", removing(1));
map.computeIfPresent("A", removing(2));
map.computeIfPresent("A", removing(3));
map.computeIfPresent("B", removing(4));
I have this piece of code:
private static void computeMapAddition(Map<String, List<XXX>> objectMap,
XXX objectToAdd, String key) {
if (objectMap.containsKey(key)) {
List<XXX> objectList = objectMap
.get(key);
objectList.add(objectToAdd);
} else {
List<XXX> objectList = new ArrayList<>();
objectList.add(objectToAdd);
objectMap.put(key, objectList);
}
}
What this code does:
1) if map contains key then retrieve value - which is List - and add element to that list (it can have lots of elements already)
2) if map doesn't contain a key then create new list, add element to the newly created list and put that (key, value) to the map
Is there any way to make it less verbose using Java 8?
java 8 added computeIfAbsent to Map interface. It does exactly what you want:
// return the list if already present or make a new one, insert into the map
// and return the newly created one
List<XXX> objectList = objectMap.computeIfAbsent(key, k -> new ArrayList<>());
// add new object to list
objectList.add(objectToAdd);
Or you can combine it together as
objectMap.computeIfAbsent(key, k -> new ArrayList<>()).add(objectToAdd);
Some addition to #misha answer.
We may use two options:
Map<Integer, List<String>> map = new HashMap<>();
computeIfAbsent (preferred)
map.computeIfAbsent(1, ArrayList::new).add("b");
compute
map.compute(1, (k, v) -> {
v = v != null ? v : new ArrayList<>();
v.add("b");
return v;
});
This work for me:
map.compute(key, (k, v) -> {
v = v != null ? new ArrayList<>(v) : new ArrayList<>();
v.add("text");
return v;
});
Let's say I have a HashMap with String keys and Integer values:
map = {cat=1, kid=3, girl=3, adult=2, human=5, dog=2, boy=2}
I want to switch the keys and values by putting this information into another HashMap. I know that a HashMap cannot have duplicate keys, so I tried to put the information into a HashMap with the Integer for the keys that would map to a String ArrayList so that I could potentially have one Integer mapping to multiple Strings:
swap = {1=[cat], 2=[adult, dog, boy], 3=[kid, girl], 5=[human]}
I tried the following code:
HashMap<Integer, ArrayList<String>> swap = new HashMap<Integer, ArrayList<String>>();
for (String x : map.keySet()) {
for (int i = 0; i <= 5; i++) {
ArrayList<String> list = new ArrayList<String>();
if (i == map.get(x)) {
list.add(x);
swap.put(i, list);
}
}
}
The only difference in my code is that I didn't hard code the number 5 into my index; I have a method that finds the highest integer value in the original HashMap and used that. I know it works correctly because I get the same output even if I hard code the 5 in there, I just didn't include it to save space.
My goal here is to be able to do this 'reversal' with any set of data, otherwise I could just hard code the value. The output I get from the above code is this:
swap = {1=[cat], 2=[boy], 3=[girl], 5=[human]}
As you can see, my problem is that the value ArrayList is only keeping the last String that was put into it, instead of collecting all of them. How can I make the ArrayList store each String, rather than just the last String?
With Java 8, you can do the following:
Map<String, Integer> map = new HashMap<>();
map.put("cat", 1);
map.put("kid", 3);
map.put("girl", 3);
map.put("adult", 2);
map.put("human", 5);
map.put("dog", 2);
map.put("boy", 2);
Map<Integer, List<String>> newMap = map.keySet()
.stream()
.collect(Collectors.groupingBy(map::get));
System.out.println(newMap);
The output will be:
{1=[cat], 2=[adult, dog, boy], 3=[kid, girl], 5=[human]}
you are recreating the arrayList for every iteration and i can't figure out a way to do it with that logic, here is a good way though and without the need to check for the max integer:
for (Map.Entry<String, Integer> entry : map.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
List<String> get = swap.get(value);
if (get == null) {
get = new ArrayList<>();
swap.put(value, get);
}
get.add(key);
}
Best way is to iterate over the key set of the original map.
Also you have to asure that the List is present for any key in the target map:
for (Map.Entry<String,Integer> inputEntry : map.entrySet())
swap.computeIfAbsent(inputEntry.getValue(),()->new ArrayList<>()).add(inputEntry.getKey());
This is obviously not the best solution, but approaches the problem the same way you did by interchanging inner and outer loops as shown below.
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("cat", 1);
map.put("kid", 3);
map.put("girl", 3);
map.put("adult", 2);
map.put("human", 5);
map.put("dog", 2);
map.put("boy", 2);
HashMap<Integer, ArrayList<String>> swap = new HashMap<Integer, ArrayList<String>>();
for (Integer value = 0; value <= 5; value++) {
ArrayList<String> list = new ArrayList<String>();
for (String key : map.keySet()) {
if (map.get(key) == value) {
list.add(key);
}
}
if (map.containsValue(value)) {
swap.put(value, list);
}
}
Output
{1=[cat], 2=[adult, dog, boy], 3=[kid, girl], 5=[human]}
Best way I can think of is using Map.forEach method on existing map and Map.computeIfAbsent method on new map:
Map<Integer, List<String>> swap = new HashMap<>();
map.forEach((k, v) -> swap.computeIfAbsent(v, k -> new ArrayList<>()).add(k));
As a side note, you can use the diamond operator <> to create your new map (there's no need to repeat the type of the key and value when invoking the map's constructor, as the compiler will infer them).
As a second side note, it's good practice to use interface types instead of concrete types, both for generic parameter types and for actual types. This is why I've used List and Map instead of ArrayList and HashMap, respectively.
Using groupingBy like in Jacob's answer but with Map.entrySet for better performance, as suggested by Boris:
// import static java.util.stream.Collectors.*
Map<Integer, List<String>> swap = map.entrySet()
.stream()
.collect(groupingBy(Entry::getValue, mapping(Entry::getKey, toList())));
This uses two more methods of Collectors: mapping and toList.
If it wasn't for these two helper functions, the solution could look like this:
Map<Integer, List<String>> swap = map.entrySet()
.stream()
.collect(
groupingBy(
Entry::getValue,
Collector.of(
ArrayList::new,
(list, e) -> {
list.add(e.getKey());
},
(left, right) -> { // only needed for parallel streams
left.addAll(right);
return left;
}
)
)
);
Or, using toMap instead of groupingBy:
Map<Integer, List<String>> swap = map.entrySet()
.stream()
.collect(
toMap(
Entry::getValue,
(e) -> new ArrayList<>(Arrays.asList(e.getKey())),
(left, right) -> {
left.addAll(right);
return left;
}
)
);
It seams you override the values instrad of adding them to the already creared arraylist. Try this:
HashMap<Integer, ArrayList<String>> swapedMap = new HashMap<Integer, ArrayList<String>>();
for (String key : map.keySet()) {
Integer swappedKey = map.get(key);
ArrayList<String> a = swapedMap.get(swappedKey);
if (a == null) {
a = new ArrayList<String>();
swapedMap.put(swappedKey, a)
}
a.add(key);
}
I didn't have time to run it (sorry, don't have Java compiler now), but should be almost ok :)
You could use the new merge method in java-8 from Map:
Map<Integer, List<String>> newMap = new HashMap<>();
map.forEach((key, value) -> {
List<String> values = new ArrayList<>();
values.add(key);
newMap.merge(value, values, (left, right) -> {
left.addAll(right);
return left;
});
});