7

User Detail model:

private String userName;
private int userSalary;

I've a ArrayList of user information

List<UserDetail> userDetails = new ArrayList<>();

UserDetail user1 = new UserDetail("Robert", 100);
UserDetail user2 = new UserDetail("John", 100);
UserDetail user3 = new UserDetail("Robert", 55);

userdetails.add(user1);
userdetails.add(user2);
userdetails.add(user3);

I'm trying to iterate through the array and find out if there are any duplicate entries based on userName, from the above list I've two records with same user name "Robert", in this case I want to add up the userSalary and remove one record from the List.

Expected new ArrayList:

userName userSalary

Robert 155
John 100

Is this possible ??

Holger
  • 285,553
  • 42
  • 434
  • 765
Ramana
  • 1,692
  • 3
  • 18
  • 34

5 Answers5

2
 userDetails.stream()
            .collect(Collectors.toMap(
                        UserDetail::getName,
                        Function.identity(),
                        (left, right) -> {
                            left.setSalary(left.getSalary() + right.getSalary());
                            return left;
                        }
                    ))
            .values();

This will give you a Collection<UserDetail>. You can copy that into an ArrayList if needed, obviously.

Eugene
  • 117,005
  • 15
  • 201
  • 306
1

Because your goal is to group UserDetail objects that share the same userName, I recommend storing the result in a Map instead of an ArrayList.

This is possible by streaming your List and collecting it to a Map using Collectors#groupingBy in conjunction with Collectors#summingInt:

List<UserDetail> userDetails = new ArrayList<>();

UserDetail user1 = new UserDetail("Robert", 100);
UserDetail user2 = new UserDetail("John", 100);
UserDetail user3 = new UserDetail("Robert", 55);

userDetails.add(user1);
userDetails.add(user2);
userDetails.add(user3);

Map<String, Integer> groupedUserDetails = userDetails.stream()
    .collect(Collectors.groupingBy(UserDetail::getUserName,
        Collectors.summingInt(UserDetail::getUserSalary)));

System.out.println(groupedUserDetails);

This above snippet may output the following:

{Robert=155, John=100}

If you want to convert this Map<String, Integer> into a List<UserDetail>, then you can use the following:

List<UserDetail> newUserDetails = groupedUserDetails.entrySet()
    .stream()
    .map(entry -> new UserDetail(entry.getKey(), entry.getValue())
    .collect(Collectors.toList());
Jacob G.
  • 28,856
  • 5
  • 62
  • 116
1

Here is a solution without streams(may be easier to understand):

Iterator<UserDetail> it=userDetails.iterator();
Map<String,UserDetail> found=new HashMap<>();
while(it.hasNext()){
    UserDetail next=it.next();
    if(found.containsKey(next.getUserName())){
        found.get(next.getUserName()).setUserSalery(found.get(next.getUserName()).getUserSalery()+next.getUserSalery();
        it.remove();
    }
    else{
        found.put(next.getUserName(),next);
    }
}

This iterates through all elements.

If it has already found a matching element, it adds its own salery to it and removes itself out of the list.

If not, it marks itself to be found if other elemts are found with the same name later.

This assumes that UserDetail has standard getter/setter methods for userName and userSalery.

Note that a for-each loop cannot be used because you cannot modify the content of the List in there (it would throw a ConcurrentModificationException).

From the comments(from @Holger:

You can use a single UserDetail previous = found.putIfAbsent(next.getName());, followed by if(previous != null) { previous.setSalery(previous.getSalery()+next.getSalery()); it.remove(); } instead of looking up the map three times in a row.

That code would be:

Iterator<UserDetail> it=userDetails.iterator();
Map<String,UserDetail> found=new HashMap<>();
while(it.hasNext()){
    UserDetail next=it.next();
    UserDetail previous = found.putIfAbsent(next.getUserName());
    if(previous != null) {
        previous.setUserSalery(previous.getUserSalery()+next.getUserSalery());
        it.remove();
    }
}

This essentially does the same thing.

It adds the current element to the List if it does not exist and if not, it just adds up the salery.

dan1st
  • 12,568
  • 8
  • 34
  • 67
  • 3
    You can use a single `UserDetail previous = found.putIfAbsent(next.getName());`, followed by `if(previous != null) { previous.setSalery(previous.getSalery()+next.getSalery()); it.remove(); }` instead of looking up the map three times in a row. – Holger Mar 03 '20 at 17:06
  • @Holger I've added it. – dan1st Mar 03 '20 at 17:32
0

I can suggest an alternate approach. HashMap

  • Use username as key
  • override equals & hashcode
  • Before adding an element to the hashMap, check if any object already present
  • If object found, get salary from it, update the current object, and then add to map

public class UserDetail {
    private String userName;
    private int userSalary;

    @Override
    public boolean equals(Object obj) {
        return this.userName.equals(((UserDetail)obj).userName);
    }

    @Override
    public int hashCode() {
        return userName.length();
    }
}
Aditya Rewari
  • 2,343
  • 2
  • 23
  • 36
0

Here Map is used to store UserDetail by userName. If user is in map salary will be add up and update in the map. Other wise user will be put in the map. Finally the map values will be converted to a UserDetail list

Map<String, UserDetail> mergedMap = new HashMap<>(); // username is used as the key 
userDetails.forEach(userDetail -> {
   String userName = userDetail.getUserName();
   UserDetail userInMap = mergedMap.get(userName);
   if (userInMap != null) { // if user is in map salary will be added
      userInMap.setUserSalary(userInMap.getUserSalary() + userDetail.getUserSalary());
   } else { //otherwise user will put in map
      mergedMap.put(userDetail.getUserName(), userDetail);
   }
});

List<UserDetail> usersWithMergedSalaries = new ArrayList<>(mergedMap.values()); //convert map values to a list
lakshman
  • 2,641
  • 6
  • 37
  • 63