2

I uttlerly convinced that my question its quite simple but im unable to do it with streams (if there is a way to do it without stream will be helpfull too) Suppose that we have this list of users

public class Users {
   String firstName;
   String lastName;
   double accountBalance;
   String type;
   String extraField;
}

and suppose that we have the following data in my List < Users >

"Users": [{
            "firstName": "Scott",
            "lastName": "Salisbury",
            "accountBalance": "100",
            "type" : "A"
        }, {
            "firstName": "John",
            "lastName": "Richards",
            "accountBalance": "200",
            "type" :"C"

        }, {
            "firstName": "John",
            "lastName": "Richards",
            "accountBalance": "200",
            "type " : "C",
            "ExtraField": "Apply"
        }]

the expected result here its given that firstName, lastName and type appears twice on the list just merge the results that are common without missing any field
Expected output

"Users": [{
            "firstName": "Scott",
            "lastName": "Salisbury",
            "accountBalance": "100",
            "type" : "A"
        }, {
            "firstName": "John",
            "lastName": "Richards",
            "accountBalance": "400",//merged values
            "type " : "C",
            "ExtraField": "Apply" //value that remains in one object of the list
        }]  
  • How many fields could be there in the bean? – Govinda Sakhare Feb 26 '20 at 14:05
  • @GovindaSakhare this a example , with this supose 5 fields – Guillermo Nahuel Varelli Feb 26 '20 at 14:06
  • Are you using any libraries for manipulating this data? For example, [JSON-Simple](https://code.google.com/archive/p/json-simple/)? – Tim Hunter Feb 26 '20 at 14:12
  • @TimHunter what i what to merge its just a List , i will update to give more details – Guillermo Nahuel Varelli Feb 26 '20 at 14:15
  • 1
    You can first group them using `groupingBy` with a function extracting the key fields; this gives you a list of users by key. Those you can then merge by giving a downstream collector. – daniu Feb 26 '20 at 14:22
  • All you need to do is implement a method `public User mergeSimilarUsers(User user, User anotherUser);`, rest would be a simple *collection to a `Map`* based on *keys for uniqueness* and *extracting the values* from it as a result. Give – Naman Feb 26 '20 at 14:29
  • @daniu Or simply use `toMap` with the method as suggested in my previous comment. – Naman Feb 26 '20 at 14:31
  • @Naman I keep forgetting `toMap` because I learned `groupingBy` first :/ – daniu Feb 26 '20 at 14:32
  • 1
    @daniu Sure, let's [remember it again](https://stackoverflow.com/questions/57041896/java-streams-replacing-groupingby-and-reducing-by-tomap) then. – Naman Feb 26 '20 at 14:33

2 Answers2

7

You can create a key class containing the three fields, like

@Data
class UserKey {
    String firstName;
    String lastName;
    String type;

    static UserKey from(User user) { /* TODO (trivial) */ }
}

groupingBy

Those can be used to group your users

Map<UserKey,List<User>> grouped = 
    users.stream().collect(Collectors.groupingBy(UserKey::from));

Each of these lists can then be merged by

Optional<User> summed = userList.stream()
    .collect(Collectors.reducing((u1, u2) -> {
        u1.setAccountBalance(u1.accountBalance() + u2.accountBalance());
    });

This can also be given directly as a downstream collector to the groupingBy:

Map<UserKey,Optional<User>> mergedMap = 
    users.stream().collect(Collectors.groupingBy(UserKey::from,
        Collectors.reducing((u1, u2) -> {
            u1.setAccountBalance(u1.accountBalance() + u2.accountBalance());
            return u1;
        }));

Since those Optionals are guaranteed to be filled, you can just call get() on them; also, you don't need the keys anymore, so

List<User> result = mergedMap.values().stream()
                 .map(Optional::get)
                 .collect(toList());

toMap

As Naman suggested in the comments, you can also shortcut this by toMap.

Map<UserKey,User> mergedMap = users.stream()
    .collect(toMap(UserKey::from, Function.identity(), 
        (u1, u2) -> {
            u1.setAccountBalance(u1.accountBalance() + u2.accountBalance());
            return u1;
        }));
List<User> result = new ArrayList<>(mergedMap.values());

Note that the reducing function has the side effect of manipulating one of the original user objects in the list, so make sure you don't need them again.

daniu
  • 14,137
  • 4
  • 32
  • 53
  • 1
    Isn't that Collectors.toMap() ?? and I'm getting Cannot resolve method 'identity' error, any idea ?? – Ramana Mar 03 '20 at 19:33
  • 1
    You should not use a method reference. You should use Function.identity(). If you use a method reference, you're basically trying to pass a Supplier>. – Ramana Mar 03 '20 at 19:42
  • @Ramana corrected, thanks. I use and imply static imports for the `Collectors` methods. – daniu Mar 03 '20 at 19:50
  • First of all UserKey Method "from" is not trivial for those who need help here. And Second you have a ")" too much in every example. – NicDD4711 Aug 18 '22 at 17:05
1

If the data is just Lists of Objects, then you should be able to merge the data fairly straight forward with a few loops. For example:

public static ArrayList<User> mergeData(ArrayList<User> userList) {
  ArrayList<User> users = new ArrayList<>(userList);

  for(int i = 0; i < users.size(); i++) {
      User currentUser = users.get(i);
      for(int j = i + 1; j < users.size(); j++) {
          User otherUser = users.get(j);

          if(currentUser.firstName.equals(otherUser.firstName) 
            && currentUser.lastName.equals(otherUser.lastName)
            && currentUser.type.equals(otherUser.type)) {
                //Apply the field merging
                currentUser.accountBalance += otherUser.accountBalance;
                if(currentUser.extraField == null) {
                    currentUser.extraField = otherUser.extraField;
                } else {
                    //Handle case where you pick whether to keep the value or not.
                }

                //Remove the merged data and move index back to account for removal
                users.remove(j);
                j--;
           }
      }
  }

  return users;
}

The code just runs through the values, compares it against every other remaining value in the list, merges where applicable, and then removes the merged object from the rest of the list before moving on to the next value.

Tim Hunter
  • 826
  • 5
  • 10