Присвоить уникальное значение полю в группе повторяющихся записей во время groupingBy

avatar
Adalberto José Brasaca
8 августа 2021 в 18:32
138
3
1

Согласно ответу, предоставленному devReddit здесь, я сгруппировал записи CSV (одинаковые имена клиентов) из следующего тестового файла (поддельные данные):

тестовый CSV-файл

id,name,mother,birth,center
1,Antonio Carlos da Silva,Ana da Silva, 2008/03/31,1
2,Carlos Roberto de Souza,Amália Maria de Souza,2004/12/10,1
3,Pedro de Albuquerque,Maria de Albuquerque,2006/04/03,2
4,Danilo da Silva Cardoso,Sônia de Paula Cardoso,2002/08/10,3
5,Ralfo dos Santos Filho,Helena dos Santos,2012/02/21,4
6,Pedro de Albuquerque,Maria de Albuquerque,2006/04/03,2
7,Antonio Carlos da Silva,Ana da Silva, 2008/03/31,1
8,Ralfo dos Santos Filho,Helena dos Santos,2012/02/21,4
9,Rosana Pereira de Campos,Ivana Maria de Campos,2002/07/16,3
10,Paula Cristina de Abreu,Cristina Pereira de Abreu,2014/10/25,2
11,Pedro de Albuquerque,Maria de Albuquerque,2006/04/03,2
12,Ralfo dos Santos Filho,Helena dos Santos,2012/02/21,4

Субъект клиента

package entities;

public class Client {

    private String id;
    private String name;
    private String mother;
    private String birth;
    private String center;
    
    public Client() {
    }

    public Client(String id, String name, String mother, String birth, String center) {
        this.id = id;
        this.name = name;
        this.mother = mother;
        this.birth = birth;
        this.center = center;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getMother() {
        return mother;
    }

    public void setMother(String mother) {
        this.mother = mother;
    }

    public String getBirth() {
        return birth;
    }

    public void setBirth(String birth) {
        this.birth = birth;
    }

    public String getCenter() {
        return center;
    }

    public void setCenter(String center) {
        this.center = center;
    }
        
    @Override
    public String toString() {
        return "Client [id=" + id + ", name=" + name + ", mother=" + mother + ", birth=" + birth + ", center=" + center
                + "]";
    }
        
}

Программа

package application;
    
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
    
import entities.Client;
    
public class Program {
    
    public static void main(String[] args) throws IOException {
            
        Pattern pattern = Pattern.compile(",");
            
        List<Client> file = Files.lines(Paths.get("src/Client.csv"))  
            .skip(1)
            .map(line -> { 
                String[] fields = pattern.split(line);
                return new Client(fields[0], fields[1], fields[2], fields[3], fields[4]);
            })
            .collect(Collectors.toList()); 
                        
        Map<String, List<Client>> grouped = file
            .stream()
            .filter(x -> file.stream().anyMatch(y -> isDuplicate(x, y)))
            .collect(Collectors.toList())
            .stream()
            .collect(Collectors.groupingBy(p -> p.getCenter(), LinkedHashMap::new, Collectors.mapping(Function.identity(), Collectors.toList())));

        grouped.entrySet().forEach(System.out::println);    
    }
}

private static Boolean isDuplicate(Client x, Client y) {

    return !x.getId().equals(y.getId())
    && x.getName().equals(y.getName())
    && x.getMother().equals(y.getMother())
    && x.getBirth().equals(y.getBirth());    
}

Окончательный результат (сгруппировано по центру)

1=[Client [id=1, name=Antonio Carlos da Silva, mother=Ana da Silva, birth= 2008/03/31, center=1],
    Client [id=7, name=Antonio Carlos da Silva, mother=Ana da Silva, birth= 2008/03/31, center=1]]
2=[Client [id=3, name=Pedro de Albuquerque, mother=Maria de Albuquerque, birth=2006/04/03, center=2],
    Client [id=5, name=Ralfo dos Santos Filho, mother=Helena dos Santos, birth=2012/02/21, center=2],
    Client [id=6, name=Pedro de Albuquerque, mother=Maria de Albuquerque, birth=2006/04/03, center=2],
    Client [id=8, name=Ralfo dos Santos Filho, mother=Helena dos Santos, birth=2012/02/21, center=2],
    Client [id=11, name=Pedro de Albuquerque, mother=Maria de Albuquerque, birth=2006/04/03, center=2],
    Client [id=12, name=Ralfo dos Santos Filho, mother=Helena dos Santos, birth=2012/02/21, center=2]]

Что мне нужно

Мне нужно присвоить уникальное значение каждой группе повторяющихся записей, начиная с каждого изменения значения центра времени, даже сохраняя записи вместе, поскольку карта не гарантирует этого, согласно приведенному ниже примеру:

Цифры слева показывают группировку по центру (1 и 2). Повторяющиеся имена имеют один и тот же номер внутренней группы и начинаются с «1». Когда центральный номер изменяется, номера внутренних групп должны быть снова перезапущены с «1» и т. д.

    1=[Client [group=1, id=1, name=Antonio Carlos da Silva, mother=Ana da Silva, birth= 2008/03/31, center=1],
       Client [group=1, id=7, name=Antonio Carlos da Silva, mother=Ana da Silva, birth= 2008/03/31, center=1]]

 // CENTER CHANGED (2) - Restart inner group number to "1" again.

    2=[Client [group=1, id=3, name=Pedro de Albuquerque, mother=Maria de Albuquerque, birth=2006/04/03, center=2],
       Client [group=1, id=6, name=Pedro de Albuquerque, mother=Maria de Albuquerque, birth=2006/04/03, center=2],
       Client [group=1, id=11, name=Pedro de Albuquerque, mother=Maria de Albuquerque, birth=2006/04/03, center=2],
 
// NAME CHANGED, BUT SAME CENTER YET - so increases by "1" (group=2)
      
Client [group=2, id=5, name=Ralfo dos Santos Filho, mother=Helena dos Santos, birth=2012/02/21, center=2],
       Client [group=2, id=8, name=Ralfo dos Santos Filho, mother=Helena dos Santos, birth=2012/02/21, center=2],
       Client [group=2, id=12, name=Ralfo dos Santos Filho, mother=Helena dos Santos, birth=2012/02/21, center=2]]
Источник

Ответы (3)

avatar
Gautham M
9 августа 2021 в 06:31
0

Вместо использования file.stream внутри каждого filter можно создать карту, сформировав ключ с помощью соответствующих полей:

Новый метод в классе Client

public String getKey() {
    return String.format("%s~%s~%s~%s", id, name, mother, birth);
}

Используйте это, чтобы создать карту со счетчиком в качестве значения.

Map<String, Long> countMap = 
    file.stream()
        .map(Client::getKey)
        .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));

Затем

// For each inner group you need a separate id based on the name.
// The input would be a map with client name as the key and the
// value would be the corresponding list of clients.
// The below function returns a new map with 
// integer as the key part (required unique id for each inner group).
Function<Map<String, List<Client>>  Map<Integer, List<Client>>> mapper
    = map -> {
        AtomicInteger i = new AtomicInteger(1);
        return map.entrySet().stream()
                  .collect(Collectors.toMap(e -> i.getAndIncrement(), Map.Entry::getValue);
    };

// assuming static import of "java.util.stream.Collectors"
Map<String, Map<Integer, List<Client>>> grouped = 
    file.stream()
        .filter(x -> countMap.get(x.getKey()) > 1L) // indicates duplicate
        .collect(groupingBy(Client::getCenter,    
                            collectingAndThen(groupingBy(Client::getName, toList()),
                                              mapper /* the above function*/ )));

Adalberto José Brasaca
9 августа 2021 в 11:50
0

Привет... Большое спасибо за ваш ответ. Представленное вами решение перезапускает нумерацию внутренних групп при изменении номера центра? Я отредактировал последнюю часть темы (таблицу) с комментариями для большей ясности.

Gautham M
9 августа 2021 в 11:56
0

@AdalbertoJoséBrasaca Да, функция mapper вызывается для каждой группы name. Таким образом, он перезапустится с 1.

panagiotis
9 августа 2021 в 18:38
1

+1 за отличную функцию картографа; @AdalbertoJoséBrasaca это решение более эффективно, когда вам нужно обрабатывать большие объемы данных, поскольку оно имеет временную сложность O (n).

Adalberto José Brasaca
10 августа 2021 в 13:41
0

@GauthamM Спасибо, что помогли мне, Гаутам ... Я узнаю что-то новое с вашим замечательным кодом. [] с.

Adalberto José Brasaca
10 августа 2021 в 16:51
1

@GauthamM Привет, Гаутам. Последняя часть кода представила 3 ​​ошибки. Я отредактировал тему и добавил в конец. Не могли бы вы взглянуть? Спасибо.

Gautham M
11 августа 2021 в 04:39
0

@AdalbertoJoséBrasaca Один ) был случайно добавлен в ответ (после Client::getCenter). collectingAndThen является аргументом внешнего groupingBy. Я обновил ответ.

Adalberto José Brasaca
11 августа 2021 в 11:39
0

@GauthamM Привет, Гаутам ... Извините за неудобства, но вы смогли протестировать код? Появилась еще одна ошибка, и для ее исправления мне нужно было изменить сигнатуру карты с Map<String, Map<String, List<Client>>> на Map<String, Map<Integer, List<Client>>>. Однако, когда я запустил программу, размер карты дает мне "0" - grouped.size(). Есть идеи ?

Gautham M
11 августа 2021 в 11:49
0

@AdalbertoJoséBrasaca Да, это было проверено. Но, как вы сказали, я использовал Integer вместо String для внутренней карты. Я попытался с одним из моих классов pojo, похожим на Client.

Gautham M
11 августа 2021 в 12:16
0

@AdalbertoJoséBrasaca Проблема связана с логикой в ​​getKey, поскольку id также включено, каждые 11 записей в csv будут обрабатываться по-разному. Следовательно, ни один из элементов не будет удовлетворять условию filter(x -> countMap.get(x.getKey()) > 1L). Вы можете удалить id из getKey и попробовать.

Adalberto José Brasaca
11 августа 2021 в 13:19
0

@GauthamM ДА!!! Это сработало так, как мне нужно. Еще раз спасибо, Гаутам, и извините за настойчивость и дополнительную работу.

avatar
JinJune
29 марта 2022 в 03:07
0

Задача требует сгруппировать файл CSV по центру и отсортировать имя в каждой группе в порядке возрастания. Код будет очень длинным, если вы попытаетесь сделать это на Java.

Это легко сделать с помощью SPL, пакета Java с открытым исходным кодом. Достаточно одной строчки кода:

А
1 =file("client.csv":"UTF-8").import@ct().sort(center,name).derive(ranki(name;center):group)

SPL предлагает драйвер JDBC для вызова из Java. Просто сохраните приведенный выше сценарий SPL как плотное_rank.splx и вызовите его в Java, как вы вызываете хранимую процедуру:

…
Class.forName("com.esproc.jdbc.InternalDriver");
con= DriverManager.getConnection("jdbc:esproc:local://");
st=con.prepareCall("call dense_rank ()");
st.execute();
…

Или выполнить строку SPL в программе Java, когда мы выполняем оператор SQL:

…
st = con.prepareStatement("==file(\"client.csv\":\"UTF-8\")
     .import@ct().sort(center,name).derive(ranki(name;center):group)");
st.execute();
…
avatar
panagiotis
8 августа 2021 в 20:30
0

Если я правильно понял, вам нужно отсортировать уже сгруппированные записи на основе всех трех свойств name, mother и birth. Вы можете применить такой порядок перед сбором с помощью groupingBy, используя sorted:

 Map<String, List<Client>> grouped = file.stream()
                    .filter(x -> file.stream().anyMatch(y -> isDuplicate(x, y)))
                    .sorted(Comparator.comparing(Client::getName)
                                      .thenComparing(Client::getMother)
                                      .thenComparing(Client::getBirth))
                    .collect(Collectors.groupingBy(Client::getCenter));

Collectors.groupingBy внутренне использует Collectors.toList() в качестве нижестоящего, поэтому он сохраняет порядок, который вы уже определили с помощью sorted; тогда нет необходимости в LinkedHashMap.

Обновление: Чтобы сгенерировать groupId, вы можете сгенерировать его из объекта Client. Ниже представлен обновленный Client:

.
package com.example.demo;

import java.util.Optional;

public class Client {

    private String id;
    private String name;
    private String mother;
    private String birth;
    private String center;
    private String groupId;

    public Client() {
    }

    public Client(String id, String name, String mother, String birth, String center) {
        this.id = id;
        this.name = name;
        this.mother = mother;
        this.birth = birth;
        this.center = center;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getMother() {
        return mother;
    }

    public void setMother(String mother) {
        this.mother = mother;
    }

    public String getBirth() {
        return birth;
    }

    public void setBirth(String birth) {
        this.birth = birth;
    }

    public String getCenter() {
        return center;
    }

    public void setCenter(String center) {
        this.center = center;
    }

    public Optional<String> getGroupId() {
        return Optional.ofNullable(groupId);
    }

    public void setGroupId(final String groupId) {
        this.groupId = groupId;
    }

    @Override
    public String toString() {
        return getGroupId().isPresent()
                ? "Client [groupId=" + groupId + ", id=" + id + ", name=" + name + ", mother=" + mother + ", birth=" + birth +
                ", center=" + center + "]"
                : "Client [id=" + id + ", name=" + name + ", mother=" + mother + ", birth=" + birth + ", center=" + center + "]";
    }
    
    ///
    /// Other public methods
    ///

    public Client generateAndAssignGroupId() {
        setGroupId(String.format("**group=%s**", center));
        return this;
    }
}

и новый поток:

Map<String, List<Client>> grouped = file.stream()
                .filter(x -> file.stream().anyMatch(y -> isDuplicate(x, y)))
                .sorted(Comparator.comparing(Client::getName).thenComparing(Client::getMother).thenComparing(Client::getBirth))
                .collect(Collectors.groupingBy(Client::getCenter,
                        Collectors.mapping(Client::generateAndAssignGroupId, Collectors.toList())));
Adalberto José Brasaca
8 августа 2021 в 21:57
0

Здравствуйте... Большое спасибо за ваш ответ. Первую часть проблемы вы решили!!! Нельзя ли каждой группе повторяющихся записей присвоить уникальное значение, как показано в последней таблице? Я считаю, что карту нужно использовать, но я не знаю, как...

panagiotis
9 августа 2021 в 07:11
0

Пожалуйста, проверьте еще раз, я обновил ответ. Этот идентификатор группы генерируется всякий раз, когда вы создаете новую структуру. Логика генерации принадлежит классу Client, следовательно, Client::generateAndAssignGroupId.

Adalberto José Brasaca
9 августа 2021 в 11:30
0

Привет Панайотис ... Большое спасибо за ваши усилия, чтобы помочь мне. Почти готово !!! Я не думаю, что было понятно, что я имел в виду. Поэтому я отредактировал последнюю часть темы (таблицу) с комментариями. Надеюсь теперь понятно. Еще раз спасибо.