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?
1. The addAll() method to merge two lists. The addAll() method is the simplest and most common way to merge two lists. Note the order of appearance of elements matches the order in which addAll() is called.
In Java, we can use String. join(",", list) to join a List String with commas.
Both of the functions map() and flatMap are used for transformation and mapping operations. map() function produces one output for one input value, whereas flatMap() function produces an arbitrary no of values as output (ie zero or more than zero) for each input value. Where R is the element type of the new stream.
Stream of(T t) returns a sequential Stream containing a single element. Syntax : static Stream of(T t) Parameters: This method accepts a mandatory parameter t which is the single element in the Stream. Return Value: Stream of(T t) returns a sequential Stream containing the single specified element.
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 Collection
s 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())))));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With