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)
Related
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()));
I'm in a weird situation where have a JSON API that takes an array with strings of neighborhoods as keys and an array of strings of restaurants as values which get GSON-parsed into the Restaurant object (defined with a String for the neighborhood and a List<String> with the restaurants). The system stores that data in a map whose keys are the neighborhood names and values are a set of restaurant names in that neighborhood. Therefore, I want to implement a function that takes the input from the API, groups the values by neighborhood and concatenates the lists of restaurants.
Being constrained by Java 8, I can't use more recent constructs such as flatMapping to do everything in one line and the best solution I've found is this one, which uses an intermediate map to store a Set of List before concatenating those lists into a Set to be store as value in the final map:
public Map<String, Set<String>> parseApiEntriesIntoMap(List<Restaurant> restaurants) {
if(restaurants == null) {
return null;
}
Map<String, Set<String>> restaurantListByNeighborhood = new HashMap<>();
// Here we group by neighborhood and concatenate the list of restaurants into a set
Map<String, Set<List<String>>> map =
restaurants.stream().collect(groupingBy(Restaurant::getNeighborhood,
Collectors.mapping(Restaurant::getRestaurantList, toSet())));
map.forEach((n,r) -> restaurantListByNeighborhood.put(n, Sets.newHashSet(Iterables.concat(r))));
return restaurantListByNeighborhood;
}
I feel like there has to be a way do get rid of that intermediate map and do everything in one line...does someone have a better solution that would allow me to do this?
You could with Java-8 simply use toMap with a mergeFunction defined as:
public Map<String, Set<String>> parseApiEntriesIntoMap(List<Restaurant> restaurants) {
// read below about the null check
return restaurants.stream()
.collect(Collectors.toMap(Restaurant::getNeighborhood,
r -> new HashSet<>(r.getRestaurantList()), (set1, set2) -> {
set1.addAll(set2);
return set1;
}));
}
Apart from which, one should ensure that the check and the result from the first block of code from your method
if(restaurants == null) {
return null;
}
when on the other hand dealing with empty Collections and Map, it should be redundant as the above code would return empty Map for an empty List by the nature of stream and collect operation itself.
Note: Further, if you may require a much relatable code to flatMapping in your future upgrades, you can use the implementation provided in this answer.
Or a solution without using streams, in this case, would look similar to the approach using Map.merge. It would use a similar BiFunction as:
public Map<String, Set<String>> parseApiEntriesIntoMap(List<Restaurant> restaurants) {
Map<String, Set<String>> restaurantListByNeighborhood = new HashMap<>();
for (Restaurant restaurant : restaurants) {
restaurantListByNeighborhood.merge(restaurant.getNeighborhood(),
new HashSet<>(restaurant.getRestaurantList()),
(strings, strings2) -> {
strings.addAll(strings2);
return strings;
});
}
return restaurantListByNeighborhood;
}
You can also flatten the Set<List<String>> after collecting them using Collectors.collectingAndThen
Map<String, Set<String>> res1 = list.stream()
.collect(Collectors.groupingBy(Restaurant::getNeighborhood,
Collectors.mapping(Restaurant::getRestaurantList,
Collectors.collectingAndThen(Collectors.toSet(),
set->set.stream().flatMap(List::stream).collect(Collectors.toSet())))));
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;
});
I have the list as follows:
List<Map<String,Object>> mapList=new ArrayList<>();
Map<String,Object> mapObject=new HashMap<String,Object>();
mapObject.put("No",1);
mapObject.put("Name","test");
mapList.add(mapObject);
Map<String,Object> mapObject1=new HashMap<String,Object>();
mapObject1.put("No",2);
mapObject1.put("Name","test");
mapList.add(mapObject1);
and so on...
Now I want to get all the values of the key "No" as a string seperated by comma as follows:
String noList="1,2,3"
Can anyone please suggest me what may best way to do it. I know we can do it by looping but instead of looping is any other ways to do it.
Explanations inline!
mapList.stream() // stream over the list
.map(m -> m.get("No")) // try to get the key "No"
.filter(Objects::nonNull) // filter any null values in case it wasn't present
.map(Object::toString) // call toString for each object
.collect(Collectors.joining(",")); // join the values
Simply map the list:
String list = mapList.stream()
.filter(x -> x.containsKey("No")) // get only the maps that has the key
.map(x -> x.get("No").toString()) // every map will be transformed like this
.collect(Collectors.joining(",")); // joins all the elements with ","
System.out.println(list);
The use of HashMap<String, Object> suggests that it might be better to create a new class for this data. Have you considered this possibility before?
You can loop like this:
List<String> noList = new ArrayList<>(mapList.size());
for (Map<String,Object> m : mapList) {
Optional.ofNullable(m.get("No")) // get value mapped to "No" or empty Optional
.map(Object::toString)
.ifPresent(noList::add); // if not empty, add to list
}
System.out.println(String.join(",", noList));
or internally (the officially preferred version IIRC):
List<String> noList = new ArrayList<>(mapList.size());
mapList.forEach(m ->
Optional.ofNullable(m.get("No")).map(Object::toString).ifPresent(noList::add));
System.out.println(String.join(",", noList));
Now that I think of it, it's shorter than the Stream version.
Answered a pretty similar question 30 minutes ago.
You are using repeated keys. This makes it look like you don't need maps, but a class with the attributes "No", "Name", etc. If you've this class you can just iterate your instances on the list and concatenating to a String.
If no matter what you want to have your maps, simply get the values of the "No" key, but note that this is a wrong practise and you should be probably using a class instead of maps:
String res = "";
for(int i = 0; i < mapList.size(); i++) {
Map<String,Object> map = mapList.get(i);
res.concat(map.get("No"));
if(i != mapList.size() - 1)
res.concat(",");
}
PS: If you are going with the bad solution practise, use the stream alternatives in the other answers if your knowledge of stream is enough to understand them.
I am having an arraylist which contains a list of numbers. I want to get all the values from the HashMap which has the keys which are in the array list.
For example say the array list contains 1,2,3,4,5,6,7,8,9 list
I want to get all the values for the keys 1,2,3,4,5,6,7,8,9 map
So currently I am implementing
for (i=0;i<list.size;i++){
map_new.put(list.get(),map.get(list.get()))
}
Is there any efficient way to do this?
Your code basically assumes that map.get(list.get()) always returns a value, you can try the following code which first filters the not null values from the list object and then adds to the new Map:
Map<String, Integer> newMap = list.stream().
filter(key -> (map.get(key) != null)).//filter values not present in Map
collect(Collectors.toMap(t -> t, t -> map.get(t)));//now collect to a new Map
In case, if map.get(list.get()) returns null, your code creates a Map with null values in it for which you might end up doing null checks, which is not good, rather you can ensure that your newly created Map always contains a value for each key.
Assuming the signature of list and the map are as following
List<Integer> list;
Map<Integer, Integer> map;
You can use following
for(int a : list){
Integer b = map.get(a);
if(b != null)
// b is your desired value you can store in another collection
}
Which is similar to the procedure you have already used.
As you can access the map in O(1) so the complexity of this code will be O(listsize)
There is not much you can do for efficiency. Still couple of small things you can do considering code example you have given above:
1) Change your for loop to
for(Long num : list)
instead of iterating using index, this will reduce you get calls over list.
2) You can update the existing map , so that you even do not need to iterate.
map.keySet().retainAll(list);
for(Long key: map.keySet()) {
System.out.println(map.get(key));
}
With this existing map will contain only those data whose keys are present in list, but you should use it carefully depending upon rest of the code logic.
You can capitalize on the fact that the keyset of a map is backed by the map itself and modifications to the keyset will reflect back to the map itself. This way, you can use the retainAll() method of the Set interface to reduce the map with a single line of code. Here is an example:
final Map<Integer, String> m = new HashMap<Integer, String>();
m.put(1, "A");
m.put(2, "B");
m.put(3, "C");
m.put(4, "D");
m.put(5, "E");
final List<Integer> al = Arrays.asList(new Integer[] { 2, 4, 5 });
System.out.println(m);
m.keySet().retainAll(al);
System.out.println(m);
This will output:
{1=A, 2=B, 3=C, 4=D, 5=E}
{2=B, 4=D, 5=E}