Java - создать вложенное древовидное представление из файла csv

Я искал похожие вопросы и не могу найти четкого решения, поэтому надеялся, что кто-то может помочь. Я новичок в Java и пытаюсь сделать следующее, но немного застрял. Проблема: у меня есть CSV-файл с тремя полями: String, int, String. Это список организаций, показывающий 140 человек в моем отделе вместе с их менеджером, и я хочу создать вложенную древовидную структуру, показывающую всю организацию.

Поля CSV: сотрудник, numberOfDirectReports, менеджер, например. образец может быть:

Bob, 5, Dave

Dave, 2, Alice

Sam, 0, Bob

так что это говорит мне, что Алиса находится на вершине дерева, и Дейв отчитывается перед ней. У самого Дейва есть 2 прямых подчиненных, один из них — Боб. У Боба 5 прямых подчиненных, один из них — Сэм. У Сэма нет прямых подчиненных.

Алиса

- Dave
     - DavesOtherReport

     - Bob

         - Sam

         - BobsOtherReport

         - BobsOtherReport

         - BobsOtherReport

         - BobsOtherReport

Что я сделал до сих пор, так это создал класс с именем Employee с тремя переменными String employeeID, int numDirectReports, String manager. Я создал ArrayList с именем employeeList, который содержит 140 экземпляров Employee.

Я могу распечатать список сотрудников и их руководителя. Но то, что я хочу сделать, это перебрать список/массив (извините, если я немного смешиваю свою терминологию) и создать что-то вроде приведенной выше древовидной структуры для всего населения.

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


person Stephen    schedule 21.02.2019    source источник


Ответы (2)


вам нужно обновить каждого сотрудника, чтобы добавить в него список сотрудников непосредственно под ним. (Я занят написанием кода, чтобы объяснить это :))

public static class Employee{
        String name;
        int numberOfDirectReport;
        String manager;
        List<Employee> directReport;

        public Employee(String name, int numberOfDirectReport, String manager) {
            this.name = name;
            this.numberOfDirectReport = numberOfDirectReport;
            this.manager = manager;
            this.directReport = new ArrayList<>();
        }

        public boolean add(Employee employee) {
            return directReport.add(employee);
        }

        public String toString(){
            return toString("");
        }

        public String toString(String prefix){
            String result = prefix + name + "\n";
            for(Employee child : directReport){
                result += child.toString(prefix+"\t");
            }
            return result;
        }
    }

    public static void main(String[] args) {
        // step 1: loading employees, to be replace by actual call to CSV reader
        HashMap<String, Employee> employees = new HashMap<>();
        employees.put("Bob", new Employee("Bob", 5, "Dave"));
        employees.put("Dave", new Employee("Dave", 2, "Alice"));
        employees.put("Sam", new Employee("Sam", 0, "Bob"));
        employees.put("Alice", new Employee("Alice", 2, null));

        // step 1.2: adding fake employees for test completion
        for(int i = 0; i < 4; i++){
            String name = "BobOtherReport"+i;
            employees.put(name, new Employee(name, 0, "Bob"));
        }
        employees.put("DaveOtherReport", new Employee("DaveOtherReport", 0, "Dave"));

        // step 2: link employees together
        for(Employee employee : employees.values()){
            if(employee.manager != null){
                // we retrieve the manager from the map and add this employee in the list inside it
                employees.get(employee.manager).add(employee);
            }
        }

        // step 3: display the top one. Either you loop on the map to find the one without manager, either you specify it
        System.out.println(employees.get("Alice").toString());
    }

который дал вывод

Alice
    Dave
        Bob
            BobOtherReport0
            BobOtherReport2
            BobOtherReport1
            BobOtherReport3
            Sam
        DaveOtherReport

Шаг 2 можно обновить, чтобы убедиться, что менеджер известен перед добавлением ссылки, чтобы избежать NPE. Другим решением было бы создать менеджер на лету и добавить его на карту в этом же фрагменте.

if(employee.manager != null){
                // we retrieve the manager from the map and add this employee in the list inside it
                Employee manager = employees.get(employee.manager);
                if(manager != null){
                    // manager has been loaded before
                    manager.add(employee);
                }
            }
person Wisthler    schedule 21.02.2019
comment
Спасибо, Вистлер, это здорово, но когда я пытаюсь подставить вызов программе чтения CSV и прочитать значения из файла, возникает исключение нулевого указателя. Я явно что-то напутал, но не могу понять что именно. - person Stephen; 25.02.2019
comment
try(BufferedReader br = Files.newBufferedReader(pathToFile)){ String line = br.readLine(); в то время как (строка! = ноль) { String [] атрибуты = line.split (,); Строка empID = атрибуты[0]; int numCounselees = Integer.parseInt (атрибуты [1]); Строковый советник = атрибуты[2]; employee.put(empID, новый сотрудник(empID, numCounselees, советник)); строка = br.readLine(); } }catch(IOException ioe) { ioe.printStackTrace(); } System.out.println(сотрудники); - person Stephen; 25.02.2019
comment
я почти уверен, что читаю данные из файла CSV, потому что я могу вывести на консоль список имен в файле, например. {john.smith=john.smith , george.best=george.best } NB: я заменил имена выше, чтобы защитить невиновных - person Stephen; 25.02.2019
comment
но затем я получаю эту ошибку. ; где denis.law — мой корневой узел, т. е. вершина моего организационного дерева, как Алиса в примере - person Stephen; 25.02.2019
comment
значение в employee.get(...) должно совпадать со значением в csv на 100%, без проблем с регистром, без пробелов, ничего другого. Образованная догадка, что это не ваш случай :) - person Wisthler; 25.02.2019
comment
Спасибо, Вистлер, это хороший крик, и я думал об этом, но, насколько я могу судить, здесь нет ни пробелов, ни нечетных символов, и я вставил значение непосредственно из файла csv в предложение get. Я пытаюсь использовать отладчик eclipse, чтобы узнать, могу ли я получить больше информации о том, где он терпит неудачу/почему он не находит значение в хэш-карте, но я не очень хорошо знаком с отладчиком, поэтому это медленный прогресс. - person Stephen; 26.02.2019
comment
Правильно ли я заполнил хэш-карту? Я заменил ручной ввод в приведенном выше примере циклом while, который читается из csv, поэтому, возможно, я сделал это неправильно и неправильно заполнил карту, поэтому предложение get не может найти имя - person Stephen; 26.02.2019
comment
try(BufferedReader br = Files.newBufferedReader(pathToFile)){ String line = br.readLine(); в то время как (строка! = ноль) { String [] атрибуты = line.split (,); Строка empID = атрибуты[0]; int numCounselees = Integer.parseInt (атрибуты [1]); Строковый советник = атрибуты[2]; employee.put(empID, новый сотрудник(empID, numCounselees, советник)); строка = br.readLine(); } }catch(IOException ioe) { ioe.printStackTrace(); } - person Stephen; 26.02.2019
comment
не уверен, почему приведенный выше код не был вставлен в том виде, как он отформатирован в этом комментарии (я также новичок в stackoverflow, так что это кривая обучения) - person Stephen; 26.02.2019
comment
на самом деле я закомментировал строку System.out.println(employees.get(firstname.surname).toString()); и по-прежнему получаю исключение нулевого указателя, поэтому я думаю, что на самом деле ошибка обнаруживается в строке ниже: employee.get(employee.manager).add(employee); Я добавил строку прямо под ней в цикл for, чтобы увидеть, какое имя и пары менеджеров добавляются, и она останавливается после 46 из 143 записей. печатаются те же 46, поэтому я подозреваю, что сами данные вызывают проблему, но я не знаю, как углубиться в проблему и посмотреть, какая строка вызывает ее удушье - person Stephen; 26.02.2019
comment
Кажется, я заметил, какой записью захлебывается адд, но не могу понять, почему - person Stephen; 26.02.2019
comment
хорошо, я думаю, что я ближе к выяснению, в чем проблема. Корень дерева, т.е. старший менеджер, назовем ее Алиса, у нее есть менеджер, но этот менеджер является внешним по отношению к этой организации (т.е. Алиса является самым старшим лицом в этом списке, но у Алисы есть запись, которая указывает ее собственного менеджера, назовем его Адам. Адам больше нигде в этом списке не фигурирует. Его нет в столбце имени, поэтому у него нет ключа на карте. Не знаю, как решить эту проблему. - person Stephen; 26.02.2019
comment
Мне нужно как-то разрешить записи, у которых есть менеджер, которого нет в списке. Есть несколько других сотрудников-сирот, чей менеджер не находится в той же организации, и поэтому они также не будут указаны в качестве ключа на карте. есть идеи, как заполнить карту, чтобы избежать этой проблемы? - person Stephen; 26.02.2019
comment
разделить строку employees.get(employee.manager).add(employee); на две части. Сначала извлеките менеджер и убедитесь, что он не равен нулю, прежде чем вызывать для него добавление. Второй подход заключается в создании менеджера на лету, если он не найден с помощью map.getOrDefault(). - person Wisthler; 26.02.2019
comment
Я думал, что оператор if уже проверял, является ли менеджер нулевым, т.е. в списке внутри него employee.get(employee.manager).add(employee); } } так что не следует ли ему пропустить предложение if и вернуться в цикл for, если он встретит запись, в которой Алан указан как менеджер, поскольку Алан не является ключом в хэш-карте? - person Stephen; 26.02.2019
comment
на самом деле, я не уверен, что предложение if оценивается правильно, потому что я пытался изменить его на if (employee.manager != Alan) { // мы извлекаем менеджера из карты и добавляем этого сотрудника в список внутри него //employees.get(employee.manager).add(employee); System.out.println(Менеджер: + employee.manager); } - person Stephen; 26.02.2019
comment
приведенный выше код выводит Manager is Alan, но, конечно, когда цикл for достигает записи с менеджером = Alan, тогда предложение IF employee.manager != Alan должно быть ложным, и мы не должны пытаться выполнить код в IF блокировать. - person Stephen; 26.02.2019
comment
@Стивен, тебе нужно два, если. Существующий проверяет, определен ли менеджер для текущего сотрудника. Новый, о котором я говорил, — это проверка, известен ли менеджер нам или внешний. Я обновил свое решение, чтобы показать два варианта if. Взглянуть. - person Wisthler; 27.02.2019
comment
Большое спасибо за вашу помощь, Вистлер. теперь код работает, и я могу распечатать прекрасное маленькое организационное дерево. Хорошего дня и хорошего настроения за помощь :) - person Stephen; 27.02.2019

Итак, если я правильно вас понимаю, у вас будет такой класс:

public class Employee {
    private String employeeID;
    private String manager;
    int numDirectReports;
    private List<Employee> employeeList;

...
}

Несколько комментариев:

  • Почему бы не объявить manager как Employee? не было бы проще использовать?
  • поле numDirectReports избыточно: его значение должно быть равно employeeList.size().
  • Один и тот же сотрудник не должен дважды появляться в employeeList какого-либо руководителя.

Поэтому я бы предпочел объявить класс следующим образом:

public class Employee {
    private String employeeID;
    private Employee manager;
    private final Set<Employee> employeeSet = new HashSet<>();

    public String getEmployeeID() {
        return employeeID;
    }

    public void setEmployeeID(String employeeID) {
        this.employeeID = employeeID;
    }

    public Employee getManager() {
        return manager;
    }

    public void setManager(Employee manager) {
        this.manager = manager;
    }

    public Set<Employee> getEmployeeSet() {
        return new HashSet<>(employeeSet);
    }

    public void addEmployee(Employee e) {
        employeeSet.add(e);
    }
}

Теперь, чтобы одновременно загрузить CSV при построении дерева, я бы использовал Map<String,Employee>:

    Map<String,Employee> allEmployees = new HashMap<>();
    for (String[] record: csvRecords()) {
        String id = record[0];
        int redundant = Integer.parseInt(record[1]);
        String managerId = record[2];
        Employee emp = allEmployees.get(id);
        if (emp == null) {
            emp = new Employee();
            emp.setEmployeeID(id);
            allEmployees.put(id, emp);
        }
        Employee manager = null;
        if (managerId != null && managerId.length() > 0) {
            manager = allEmployees.get(managerId);
            if (manager == null) {
                manager = new Employee();
                manager.setEmployeeID(managerId);
                allEmployees.put(managerId, manager);
            }
            manager.addEmployee(emp);
        }
        emp.setManager(manager);
    }

ОБНОВИТЬ

Если вам нужно количество прямых отчетов, вы можете добавить метод:

public int numberOfDirectReports() {
    return employeeSet.size();
}
person Maurice Perry    schedule 21.02.2019
comment
Большое спасибо вам обоим. странно, Морис, когда я набираю Map‹String,Employee› allEmployees = new HashMap‹›(); Синтаксическая ошибка в токене ;, { ожидается после этого токена, который исчезает, если я удаляю точку с запятой (но тогда это будет недопустимый синтаксис) Я получаю сообщение об ошибке в eclipse - person Stephen; 21.02.2019
comment
@ Стивен, это действительно странно. Проверьте утверждение, которое предшествует. - person Maurice Perry; 22.02.2019