Преобразуйте Java List в формат Pivot - PullRequest
0 голосов
/ 08 декабря 2018

У меня есть класс ниже, и я хотел бы преобразовать список объектов данных в формат сводной таблицы с помощью Java.

public class Data {

    private String consultedOn;
    private String consultedBy;

    // Getters

    // Setters


}


List<Data> reports = new ArrayList<Data>();

reports.add(new Data("04/12/2018","Mr.Bob"));
reports.add(new Data("04/12/2018","Mr.Jhon"));
reports.add(new Data("04/12/2018","Mr.Bob"));
reports.add(new Data("05/12/2018","Mr.Jhon"));
reports.add(new Data("06/12/2018","Mr.Bob"));
reports.add(new Data("06/12/2018","Mr.Jhon"));
reports.add(new Data("07/12/2018","Mr.Bob"));

Я хотел бы преобразовать список выше в формате приведенной ниже таблицы с помощью Java.внутри коллекции.

consultedOn       Mr.Bob          Mr.Jhon  
---------------------------------------
04/12/2018           2              1
05/12/2018           0              1
06/12/2018           1              1
07/12/2018           1              0

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

Я пытался использовать потоки Java8с кодом ниже.

class DataMap {


 private String consultedOn;
    private String consultedBy;

    public DataMap(String consultedOn) {
        super();
        this.consultedOn = consultedOn;
    }

    public DataMap(String consultedOn, String consultedBy) {
        super();
        this.consultedOn = consultedOn;
        this.consultedBy = consultedBy;
    }


    public String getConsultedOn() {
        return consultedOn;
    }


    public void setConsultedOn(String consultedOn) {
        this.consultedOn = consultedOn;
    }


    public String getConsultedBy() {
        return consultedBy;
    }


    public void setConsultedBy(String consultedBy) {
        this.consultedBy = consultedBy;
    }


    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((consultedOn == null) ? 0 : consultedOn.hashCode());
        return result;
    }


    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (!(obj instanceof DataMap ))
            return false;
        DataMap other = (DataMap )obj;
        if (consultedOn == null) {
            if (other.consultedOn != null)
                return false;
        } else if (!consultedOn.equals(other.consultedOn))
            return false;
        return true;
    }


}


Map<DataMap, List<DataReport>> map = reports.stream()
            .collect(Collectors.groupingBy(x -> new DataMap(x.getConsultedOn(), x.getConsultedBy())));

Но карта не дает ожидаемых результатов в соответствии с моими ожиданиями.

Я не уверен, как поступить с такого рода данными, любая помощьбудет оценено.

Ответы [ 4 ]

0 голосов
/ 08 декабря 2018

Возможно, вы хотите использовать Collectors.groupingBy для группировки List<DataMap> по consultedOn и дальнейшей группировки по атрибуту consultedBy и их счету:

Map<String, Map<String, Long>> finalMapping = reports.stream()
        .collect(Collectors.groupingBy(DataMap::getConsultedOn,
            Collectors.groupingBy(DataMap::getConsultedBy,Collectors.counting())));

Это даст вам в качестве вывода:

{05/12/2018={Mr.Jhon=1}, 06/12/2018={Mr.Jhon=1, Mr.Bob=1},
07/12/2018={Mr.Bob=1}, 04/12/2018={Mr.Jhon=1, Mr.Bob=2}}

Кроме того, если вам требуется учесть все соответствующие значения consultedBy, вы можете создать Set из этихиз начальных List<DataMap> как:

Set<String> consultedBys = reports.stream()
        .map(DataMap::getConsultedBy)
        .collect(Collectors.toSet());

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

finalMapping.forEach((k, v) -> consultedBys.forEach(c -> v.putIfAbsent(c, 0L)));

Теперь это дастВы в качестве вывода:

{05/12/2018={Mr.Jhon=1, Mr.Bob=0}, 06/12/2018={Mr.Jhon=1, Mr.Bob=1},
07/12/2018={Mr.Jhon=0, Mr.Bob=1}, 04/12/2018={Mr.Jhon=1, Mr.Bob=2}}
0 голосов
/ 08 декабря 2018

Другой будет выглядеть так:

Map<Pair<String, String>, Integer> map = reports
      .stream()
     .collect(toMap(data -> new Pair<>(data.getConsultedOn(),
                    data.getConsultedBy()), data -> 1, Integer::sum));

Map<String, DataMap> result= new HashMap<>();

-

class DataMap {
   private String consultedOn;
   private Map<String, Integer> map;
}

-

Set<String> persons = new HashSet<>();
persons = reports.stream().map(Data::getConsultedBy).collect(Collectors.toSet());

-

for (Map.Entry<Pair<String, String>, Integer> entry : map.entrySet()) {
      Map<String, Integer> val = new HashMap<>();
      for (String person : persons) {
          if (!person.equals(entry.getKey().getValue()))
              val.put(person, 0);
           else
              val.put(entry.getKey().getValue(), entry.getValue());
       }
        result.put(entry.getKey().getKey(), new DataMap(entry.getKey().getKey(), val));
}

иокончательный результат:

List<DataMap> finalResult = new ArrayList<>(result.values());
0 голосов
/ 08 декабря 2018

Вместо того, чтобы использовать отдельную структуру данных, вы можете использовать Карту ключей в качестве consulisedOn (date или String) и иметь значение в виде списка (String или вашего собственного определенного POJO с переопределенным методом equals ().использовали карту типа Map<String, List<String>>

Все, что вам нужно, - это два метода: один для настройки отчета (addDataToReport): для каждого консультируемого (ключа) создайте список врачей, к которым обращались. См. комментарии для map.mergeиспользование

и один для отображения данных в виде отчета (printReport). Мы используем «% 10s» для правильного форматирования. Вместо println формат неявно добавляет символ новой строки

Кроме того, чтобы получить столбец отчета, нам нужен набор (список уникальных значений), doctors.add(consultedBy); будет служить нам для этой цели. Java позаботится о том, чтобы значение врачей оставалось уникальным.

public class Application {
    Set<String> doctors = new LinkedHashSet<>();

    private void addDataToReport(Map<String, List<String>> reportMap, String consultedOn, String consultedBy) {
        doctors.add(consultedBy); // set the doctors Set
        reportMap.merge(consultedOn, Arrays.asList(consultedBy)// if key = consultedOn is not there add , a new list
                , (v1, v2) -> Stream.concat(v1.stream(), v2.stream()).collect(Collectors.toList()));//else merge previous and new values , here concatenate two lists
    }

    private void printReport(Map<String, List<String>> reportMap) {
        /*Set Headers*/
        String formatting = "%10s";//give a block of 10 characters for each string to print
        System.out.format(formatting, "consultedOn");
        doctors.forEach(t -> System.out.format(formatting, t));// print data on console without an implicit new line
        System.out.println("\n---------------------------------------");
        /*Set row values*/
        for (Map.Entry<String, List<String>> entry : reportMap.entrySet()) {
            Map<String, Integer> map = new LinkedHashMap<>();
            doctors.forEach(t -> map.put(t, 0)); // initialise each doctor count on a day to 0
            entry.getValue().forEach(t -> map.put(t, map.get(t) + 1));
            System.out.format(formatting, entry.getKey());
            map.values().forEach(t -> System.out.format(formatting, t));
            System.out.println();
        }
    }

    public static void main(String[] args) {
        Application application = new Application();
        Map<String, List<String>> reportMap = new LinkedHashMap<>();
        String MR_JHON = "Mr.Jhon";
        String MR_BOB = "Mr.Bob ";
        application.addDataToReport(reportMap, "04/12/2018", MR_BOB);
        application.addDataToReport(reportMap, "04/12/2018", MR_JHON);
        application.addDataToReport(reportMap, "04/12/2018", MR_BOB);
        application.addDataToReport(reportMap, "05/12/2018", MR_JHON);
        application.addDataToReport(reportMap, "06/12/2018", MR_BOB);
        application.addDataToReport(reportMap, "06/12/2018", MR_JHON);
        application.addDataToReport(reportMap, "07/12/2018", MR_BOB);
        application.printReport(reportMap);
    }
}

Результат

consultedOn   Mr.Bob    Mr.Jhon
---------------------------------------
04/12/2018         2         1
05/12/2018         0         1
06/12/2018         1         1
07/12/2018         1         0
0 голосов
/ 08 декабря 2018

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

public class Pivot {
    private static final class Data {

        private final String consultedOn;
        private final String consultedBy;

        public Data(String consultedOn, String consultedBy) {
            this.consultedOn = consultedOn;
            this.consultedBy = consultedBy;
        }

        public String getConsultedOn() {
            return consultedOn;
        }

        public String getConsultedBy() {
            return consultedBy;
        }
    }

    private static final class Row {
        private final String consultedOn;
        private final Map<String, Integer> consultationsByPerson = new HashMap<>();

        public Row(String consultedOn) {
            this.consultedOn = consultedOn;
        }

        public void addPerson(String person) {
            consultationsByPerson.merge(person, 1, Integer::sum);
        }

        public int getConsultationsFor(String person) {
            return consultationsByPerson.getOrDefault(person, 0);
        }

        public String getConsultedOn() {
            return consultedOn;
        }
    }

    private static class PivotReport {

        private final Map<String, Row> rowsByConsultedOn = new HashMap<>();
        private SortedSet<String> persons = new TreeSet<>();

        private PivotReport() {}

        private void addData(Data d) {
            rowsByConsultedOn.computeIfAbsent(d.getConsultedOn(), Row::new).addPerson(d.getConsultedBy());
            persons.add(d.consultedBy);
        }

        public static PivotReport create(List<Data> list) {
            PivotReport report = new PivotReport();
            list.forEach(report::addData);
            return report;
        }

        public String toString() {
            String headers = "Consulted on\t" + String.join("\t", persons);
            String rows =  rowsByConsultedOn.values()
                                            .stream()
                                            .sorted(Comparator.comparing(Row::getConsultedOn))
                                            .map(this::rowToString)
                                            .collect(Collectors.joining("\n"));
            return headers + "\n" + rows;
        }

        private String rowToString(Row row) {
            return row.getConsultedOn() + "\t" +
                persons.stream()
                       .map(person -> Integer.toString(row.getConsultationsFor(person)))
                       .collect(Collectors.joining("\t"));
        }
    }


    public static void main(String[] args) {
        List<Data> list = createListOfData();
        PivotReport report = PivotReport.create(list);
        System.out.println(report);
    }

    private static List<Data> createListOfData() {
        List<Data> reports = new ArrayList<Data>();

        reports.add(new Data("04/12/2018","Mr.Bob"));
        reports.add(new Data("04/12/2018","Mr.Jhon"));
        reports.add(new Data("04/12/2018","Mr.Bob"));
        reports.add(new Data("05/12/2018","Mr.Jhon"));
        reports.add(new Data("06/12/2018","Mr.Bob"));
        reports.add(new Data("06/12/2018","Mr.Jhon"));
        reports.add(new Data("07/12/2018","Mr.Bob"));
        reports.add(new Data("07/12/2018","Mr.Smith"));

        return reports;
    }
}

Обратите внимание, что поскольку вы используете String вместо LocalDate для поля consultedOn, даты будут отсортированы лексикографически, а не в хронологическом порядке.Вы должны использовать соответствующий тип: LocalDate.

...