Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to concatenate grouped lists into a set in Java 8 in one line?

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?

like image 767
creposukre Avatar asked Nov 18 '19 00:11

creposukre


People also ask

How do I combine multiple lists into one in Java?

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.

How do I join a List in Java 8?

In Java, we can use String. join(",", list) to join a List String with commas.

What is the difference between MAP and flatMap?

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.

What is stream of in Java?

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.


2 Answers

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;
}
like image 123
Naman Avatar answered Oct 20 '22 04:10

Naman


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())))));
like image 26
Deadpool Avatar answered Oct 20 '22 05:10

Deadpool