3

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
        }]  
9
  • How many fields could be there in the bean? Commented Feb 26, 2020 at 14:05
  • @GovindaSakhare this a example , with this supose 5 fields Commented Feb 26, 2020 at 14:06
  • Are you using any libraries for manipulating this data? For example, JSON-Simple? Commented Feb 26, 2020 at 14:12
  • 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. Commented Feb 26, 2020 at 14:22
  • 1
    @daniu Sure, let's remember it again then. Commented Feb 26, 2020 at 14:33

2 Answers 2

8

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.

Sign up to request clarification or add additional context in comments.

4 Comments

Isn't that Collectors.toMap() ?? and I'm getting Cannot resolve method 'identity' error, any idea ??
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<Function<T, T>>.
@Ramana corrected, thanks. I use and imply static imports for the Collectors methods.
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.
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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.