Home > Software design >  How can I replace the nested loops with Stream API
How can I replace the nested loops with Stream API

Time:06-04

Is it possible to replace the nested loop in the code below with Stream API?

Map<String, List<Role>> roleByUserUsername = roles.stream()
    .collect(Collectors.groupingBy(//some foo that return userName
                Role::getRoleId,
                Collectors.toList()));

List<User> users = List.of(new User(), new User());
HashMap<String, User> userByRoleId = new HashMap<>();

for (final User user : users) {
    for (final Role roles1 : roleByUserUsername.get(user.getPassword())) {
        userByRoleId.put(roles1.getRoleId(), user);
    }
}

CodePudding user response:

Try this

users.stream().forEach(user->roleByUserUsername.get(user.getPassword()).forEach(role->userByRoleId.put(role.getRoleId(), user)));

CodePudding user response:

You need to flatten the list of roles obtained from the map roleByUsername.

By the way, there is an inconsistency: according to its name the map is meant to contain usernames as keys, but in the loop you're accessing it by providing the result returned by the call getPassword().

Then you need to apply collect as a terminal operation and provide collector toMap() as an argument in order to generate a map which associates roleId with a single user.

List<Role> roles = // initialing the list of roles
    
Map<String, List<Role>> roleByUsername = roles.stream()
    .collect(Collectors.groupingBy(Role::getRoleId));
    
List<User> users = List.of(new User(), new User());
        
Map<String, User> userByRoleId = users.stream()
    .flatMap(user -> roleByUsername.get(user.getPassword()).stream()
                .map(role -> Map.entry(role.getRoleId(), user)))
    .collect(Collectors.toMap(
                Map.Entry::getKey,
                Map.Entry::getValue
            ));

But there's a logical problem here. Assuming that roleId is unique for every role, but there could multiple users having the same role. Unless you have designed an application where every user has a unique role.

Therefore, it makes sense to change the map type to Map<String, List<User>> so that a collection of user would be mapped to each roleId.

And to achieve that, we need to change the collector from toMap to groupingBy() in conjunction with mapping() and toList().

Map<String, List<User>> userByRoleId = users.stream()
    .flatMap(user -> roleByUsername.get(user.getPassword()).stream()
                .map(role -> Map.entry(role.getRoleId(), user)))
    .collect(Collectors.groupingBy(
                Map.Entry::getKey,
                Collectors.mapping(Map.Entry::getValue,
                    Collectors.toList())
            ));

Sidenotes:

  • Collector groupingBy() has a flavor that expects only one argument - classifier function. It will store all elements mapped to the same key into a list by default. You don't need to provide toList() as a downstream.

  • Write your code against interfaces like Map, don't make it dependent on concrete implementations: What does it mean to "program to an interface"?

  • Related