Java - Объединить объекты списка с учетом условия - PullRequest
2 голосов
/ 26 февраля 2020

Я совершенно убежден, что мой вопрос довольно прост, но я не могу сделать это с потоками (если есть способ сделать это без потока, тоже будет полезно) Предположим, что у нас есть этот список пользователей

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

и предположим, что у нас есть следующие данные в моем списке

"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"
        }]

ожидаемый результат здесь, учитывая, что firstName, lastName и type появляются в списке дважды, просто объединяют общие результаты не пропуская ни одного поля
Ожидаемый результат

"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
        }]  

Ответы [ 2 ]

6 голосов
/ 26 февраля 2020

Вы можете создать ключевой класс, содержащий три поля, например

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

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

groupingBy

Они могут использоваться для группировки ваших пользователей

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

Каждый из этих списков затем может быть объединен с помощью

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

Это также может быть передано непосредственно как нижестоящий коллектор в 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;
        }));

Поскольку эти Optional s гарантированно будут заполнены, вы можете просто позвонить get() на них; кроме того, вам больше не нужны ключи, поэтому

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

toMap

Как предложил Наман в комментариях, вы также можете откорректировать это с помощью 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());

Обратите внимание, что у функции сокращения есть побочный эффект манипуляции с одним из исходных пользовательских объектов в списке, поэтому убедитесь, что они вам больше не нужны.

1 голос
/ 26 февраля 2020

Если данные представляют собой просто списки объектов, то вы сможете объединить данные довольно просто с помощью нескольких циклов. Например:

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;
}

Код просто проходит через значения, сравнивает его со всеми остальными значениями в списке, объединяет, где это применимо, а затем удаляет объединенный объект из остальной части списка, прежде чем продолжить к следующему значению.

...