Удалить дубликаты из списка объектов на основе нескольких атрибутов в Java 8 - PullRequest
1 голос
/ 28 июня 2019

Я хочу сравнить getCode & getMode и найти дубликаты записей.

Затем есть еще один атрибут продукта getVode, который всегда имеет различное значение (либо true, либо false) в обеих записях.

P1   getCode  getMode  getVode
1    001      123      true
P2   getCode  getMode  getVode
2    001      123      false

Я попробовал ниже, но он находит только дубликаты:

List<ProductModel> uniqueProducts = productsList.stream()
    .collect(Collectors.collectingAndThen(
        toCollection(() -> new TreeSet<>(
            Comparator.comparing(ProductModel::getCode)
                .thenComparing(ProductModel::getMode)
        )),
        ArrayList::new));

Поэтому, когда я нахожу дублирующиеся записи, я хочу проверить значение getVode, которое является ложным, и удалить его из списка.Любая помощь будет оценена?

Ответы [ 4 ]

4 голосов
/ 28 июня 2019

Насколько я понял, вы хотите удалять элементы, только если они являются дубликатами и их метод getVode возвращает false.

Мы можем сделать это буквально.Сначала мы должны определить, какие элементы являются дубликатами:

Map<Object, Boolean> isDuplicate = productsList.stream()
    .collect(Collectors.toMap(pm -> Arrays.asList(pm.getCode(), pm.getMode()),
                              pm -> false, (a, b) -> true));

Затем удалите элементы, удовлетворяющие условию:

productsList.removeIf(pm -> !pm.getVode()
                         && isDuplicate.get(Arrays.asList(pm.getCode(), pm.getMode())));

Или, не изменяя старый список:

List<ProductModel> uniqueProducts = new ArrayList<>(productsList);
uniqueProducts.removeIf(pm -> !pm.getVode()
                           && isDuplicate.get(Arrays.asList(pm.getCode(), pm.getMode())));

, что также можно сделать с помощью операции Stream:

List<ProductModel> uniqueProducts = productsList.stream()
    .filter(pm -> pm.getVode()
              || !isDuplicate.get(Arrays.asList(pm.getCode(), pm.getMode())))
    .collect(Collectors.toList());
3 голосов
/ 28 июня 2019

Здесь вы удаляете дубликаты независимо от значения getVode(), поскольку оно не учитывается в Comparator, переданном TreeSet.
Не легко с вашим подходом.
Вы можете создать Map<ProductModelId, List<ProductModelId>>, сгруппировав элемент в соответствии с их значениями getCode() и getMode(), которые вы можете представить классом ProductModelId.
Затем для каждой записи Map обрабатывает его: если список содержит один элемент, сохраните его, в противном случае не сохраняйте все те, которые имеют getVode(), равным false.

Map<ProductModelId, List<ProductModel>> map = 
productsList.stream()
            .collect(groupingBy(p -> new ProductModelId(p.getCode(), p.getMode());

List<ProductModel> listFiltered =
        map.values()
           .stream()
           .flatMap(l -> {
                        if (l.size() == 1) {
                          return Stream.of(l.get(0));
                        } else {
                          return l.stream().filter(ProductModel::getVode);
                        }
                    }
           )
           .collect(toList());

Обратите внимание, что ProductModelId должен переопределить equals/hashCode, учитывая значение двух полей, чтобы правильно сгруппировать их на карте:

public class ProductModelId {

    private String code;
    private String mode;

    public ProductModelId(String code, String mode) {
        this.code = code;
        this.mode = mode;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof ProductModelId)) return false;
        ProductModelId that = (ProductModelId) o;
        return Objects.equals(code, that.code) &&
                Objects.equals(mode, that.mode);
    }

    @Override
    public int hashCode() {
        return Objects.hash(code, mode);
    }
}
0 голосов
/ 28 июня 2019

Если ваш vode может быть true для нескольких экземпляров ProductModel (в противном случае, если вы ожидаете один true - это еще проще, я дам это упражнение для вас), и вы хотите сохраните их всех, может быть, это то, что вы ищете:

    List<ProductModel> models = List.of(
        new ProductModel(1, 123, false),
        new ProductModel(1, 123, true)); // just an example

    Map<List<Integer>, List<ProductModel>> map = new HashMap<>();

    models.forEach(x -> {

        map.computeIfPresent(Arrays.asList(x.getMode(), x.getCode()),
                             (key, value) -> {
                                 value.add(x);
                                 value.removeIf(xx -> !xx.isVode());
                                 return value;
                             });
        map.computeIfAbsent(Arrays.asList(x.getMode(), x.getCode()),
                            key -> {
                                List<ProductModel> list = new ArrayList<>();
                                list.add(x);
                                return list;
                            });
    });

    map.values()
       .stream()
       .flatMap(List::stream)
       .forEachOrdered(x -> System.out.println(x.getCode() + "  " + x.getMode()));

где ProductModel - это что-то вроде:

    static class ProductModel {

    private final int code;
    private final int mode;
    private final boolean vode;

    // some other fields, getters, setters

}

Это не так тривиально для достижения. Сначала нужно выяснить, есть ли дубликаты, и действовать соответствующим образом только тогда, когда они найдены. map.computeIfAbsent позаботится о том, чтобы вставить в карту ключи (Key сделан из Code/Mode, завернутого в Arrays::asList - он корректно переопределяет hashCode/equals).

Когда дубликат найден на основе этого Ключа, мы хотим действовать по нему через map.computeIfPresent. «Действовать» тоже не тривиально, учитывая, что vode может быть true в нескольких случаях (да, это мое предположение). Вы не знаете, что vode было добавлено в эту карту для предыдущего Ключа - это было false? если так, то он должен быть удален. Но нынешний false тоже? Если так, это должно быть также удалено.

0 голосов
/ 28 июня 2019

Вы можете группировать по комбинации кода и режима.И затем в функции слияния получить элемент с true vode:

 Collection<ProductModel> uniqueProducts  = products.stream()
        .collect(toMap(
                    p -> Arrays.asList(p.getCode(), p.getMode()),
                    Function.identity(),
                    (p1, p2) -> p1.getVode() ? p1 : p2))
        .values();

См. javaDoc для toMap

...