Расширенная сортировка Java 8

У меня есть объект Activity, который может быть parent или child.

class Activity {
    long modificationDate;
    Activity parentActivity;
    Set<Activity> subActivities;
    boolean active;
}

Я должен отсортировать действия, ранее загруженные из базы данных, по их modification date, но все же grouped by их родительским действиям.

Правила. Дочернее действие E1 может быть обновлено, а его родительское действие P1 — нет. Таким образом, модификационная дата E1 может быть больше, чем P1.
Если дочерний элемент обновлен, its parent has to rise вверху списка со всеми его дочерними элементами.

Пример: активность | дата модификации

E1 | 01 june
P2 | 01 june
P1 | 01 june
P3 | 02 june
E1 | 10 july

После сортировки он должен дать

P1 | 01 june
E1 | 10 july
E1 | 01 june
P3 | 02 june
P2 | 01 june

База Java

Set<Activity> actives = getRepository().findActiveActivities().stream()
        .sorted(Comparator.comparing(Activity::getModificationDate).reversed())
        .collect(Collectors.toCollection(LinkedHashSet::new));

actives = actives.stream().sorted((Activity a, Activity b) ->  {
    //sort logic
}).collect(Collectors.toCollection(LinkedHashSet::new));

Я действительно не знаю, можно ли это выполнить с помощью sql-запроса, надеюсь, кто-нибудь может предоставить мне решение.

ИЗМЕНИТЬ:

Это не школьный проект. Я делаю службу отдыха, которая предоставляет модели баз данных. Proff для тех, кто думает, что я лжец: ссылка

На данный момент запрос на получение http://localhost/activity предоставляет набор несортированных действий, таких как json. Мой веб-сайт, вызывающий этот веб-сервис, также имеет несортированные действия, и таблица действий совершенно нечитаема.


person Community    schedule 22.08.2017    source источник
comment
Я стажер, просто ищу решение после 2-х дней хэдлока, делаю сервис отдыха :[   -  person    schedule 22.08.2017
comment
ищу мир без ненавистников, я не думаю, что моя тема нарушает правила stackoverflow, мне потребовалось время, чтобы создать эту тему   -  person    schedule 22.08.2017
comment
Напишите Comparator, который будет сортировать список в соответствии с запрошенным значением для вас. Компаратор   -  person Nico    schedule 22.08.2017
comment
Вот совет: забудьте на секунду о потоках и т. д.... вернитесь к основам. Как бы вы это реализовали, используя пару циклов for и рекурсию. Затем, когда у вас есть рабочий набор, вы можете увидеть, как вы можете оптимизировать его с помощью потоков, но я бы посоветовал это только в том случае, если у вас есть наборы данных больше 1000 для сортировки.   -  person Tschallacka    schedule 22.08.2017
comment
это выглядит как домашнее задание и сформулировано как домашнее задание. Вы пишете не от первого лица. Как будто вы копируете домашнее задание, и оно так сформулировано. Что ВЫ пробовали? Почему это не работает? Каковы ваши конкретные проблемы?   -  person Menelaos    schedule 22.08.2017
comment
Я предлагаю вам прочитать тему еще раз, чтобы все знать в.   -  person    schedule 22.08.2017
comment
В вашем вопросе 2 недостатка. Пожалуйста, напишите, каков результат и чем он отличается от ожидаемого результата, который вы нам дали. Тогда, пожалуйста, будьте более конкретными, чем я действительно не знаю, может ли это быть выполнено запросом sql   -  person Nico    schedule 22.08.2017
comment
@Roman, какую базу данных ты используешь?   -  person Menelaos    schedule 22.08.2017
comment
Мое предприятие использует базы данных SQL-Server   -  person    schedule 22.08.2017
comment
@Ромейн, ты уверен, что твой отсортированный список верен? Также вы используете сортировку по убыванию, верно? (SubActivty, ParentActivity) по убыванию?   -  person Menelaos    schedule 22.08.2017
comment
@MenelaosBakopoulos, что ты имеешь в виду? На данный момент алгоритма нет. Если вы говорите о LinkedHashSet, так что да, это правильный набор, чтобы сохранить порядок моих элементов.   -  person    schedule 22.08.2017
comment
@Romain - это должно быть реализовано с помощью потоков?   -  person Schidu Luca    schedule 22.08.2017
comment
@SchiduLuca Я просто ищу логику, я могу легко сопоставить ваш результат с java 8   -  person    schedule 22.08.2017


Ответы (2)


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

List<Activity> collect = list.stream().filter(activity -> activity.getParent() == null)
            .sorted((o1, o2) -> Long.compare(o2.children.stream().max(Comparator.comparing(Activity::getUpdateDate)).orElse(o2)
                    .getUpdateDate(), o1.children.stream().max(Comparator.comparing(Activity::getUpdateDate)).orElse(o1).getUpdateDate()))
            .collect(Collectors.toCollection(LinkedList::new));

Теперь создайте List, где будет храниться результат.

List<Activity> result = collect.stream().flatMap(set ->
        Stream.concat(Stream.of(set), set.getChildren().stream()
                .sorted((o1, o2) -> Long.compare(o2.getUpdateDate(), o1.getUpdateDate())))
    ).collect(Collectors.toCollection(LinkedList::new);

Вы получите List действий, как вы хотели, сначала родительский, затем дочерний, отсортированный по updateDate и т. д.

ОБНОВЛЕНИЕ

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

Метод получения всех детей Activity

public static Stream<Activity> getChildren(Activity activity) {
    List<Activity> activities = new LinkedList<>();
    recursiveCall(activities, activity);

    return activities.stream();
}

public static void recursiveCall(List<Activity> activities, Activity activity) {
    if(activity.children.isEmpty()) {
        return;
    }

    activity.getChildren().forEach(child -> {
        activities.add(child);
        recursiveCall(activities, child);
    });
}

И используйте это так:

list.stream().filter(activity -> activity.getParent() == null)
             .flatMap(YourClass::getChildren)
             .sorted...

Сделайте то же самое, когда вы собираете все результаты внутри потока.

person Schidu Luca    schedule 22.08.2017
comment
GetModificationDate() является целым числом, а не строкой D: - person ; 22.08.2017
comment
@Romain, о, я отнесся к этому как к Date - person Schidu Luca; 22.08.2017
comment
Спасибо д. Тем не менее, не предпочтительнее ли вызывать flatMap() вместо get() для необязательного? Это может привести к исключению - person ; 22.08.2017
comment
Вы после тестирования получили java.util.NoSuchElementException: нет значения - person ; 22.08.2017
comment
Вы не можете решить NullPointerException или результат неверный? - person Schidu Luca; 22.08.2017
comment
Результат неверный, смысла нет. Может быть, я потерпел неудачу, когда решил исключение - person ; 22.08.2017
comment
что я должен вернуть, если o1.max или o2.max имеет значение null? - person ; 22.08.2017
comment
Давайте продолжим обсуждение в чате. - person ; 22.08.2017
comment
@ Халк, в этот список я также добавляю родителя finalResult.add(set);, так что простой collect не сработает. Есть способ собрать родителя и отсортировать дочерние элементы, но не могу понять :( - person Schidu Luca; 22.08.2017
comment
Хорошее решение. Я не думал, что это можно сделать так просто. - person Menelaos; 22.08.2017
comment
@SchiduLuca отличная идея! Единственным предложением (кроме форматирования) было бы выделение логики компаратора в отдельный метод - чтобы код был чище. - person Eugene; 22.08.2017
comment
Я бы добавил статический импорт, чтобы сократить этот код, но в целом мне это нравится - person xenteros; 23.08.2017
comment
У меня проблема :) Этот код не принимает дочерние элементы дочерних элементов, затем дочерние элементы дочерних элементов [...] с учетом D: Этот код работает только для 2-х уровней дерева - person ; 23.08.2017
comment
Хм, я не думал, что это закончится деревом, в этом случае все изменится. В этом случае у вас есть только один родитель, как их сортировать? может ближайший родитель? все равно как-то двусмысленно - person Schidu Luca; 23.08.2017
comment
Или, может быть, у вас есть несколько деревьев, в этом случае вы можете рекурсивно получить все его дочерние элементы и отсортировать их по дате последнего обновления. - person Schidu Luca; 23.08.2017
comment
Когда ребенок, и нам все равно, на каком уровне, родитель верхнего уровня поднимается со всеми детьми - person ; 23.08.2017
comment
Активность имеет дочерние активности. И эти детские действия могут иметь детские действия и т.д.. - person ; 23.08.2017
comment
Родители всегда наверху, потом дети сортируются - person ; 23.08.2017

вступление

Я начну с подхода SQL, а позже перейду к Java. (SQL — моя любовь).

SQL-подход

Я действительно не знаю, может ли это быть выполнено с помощью sql-запроса, надеюсь, кто-нибудь может предоставить мне решение.

Это может быть решено с использованием подхода SQL. Вот первый неоптимизированный пример:

Схема

create table Activity(
  name varchar(2),
  modificationDate int,
  parentActivity varchar(2)
);

insert into activity values('E1', 1, 'P1' );
insert into activity values('P2', 1, null );
insert into activity values('P1', 1, null );
insert into activity values('P3', 2, null );
insert into activity values('E1', 10, 'P1' );

Запрос (пример игрушки версии 1)

SELECT *
FROM
  ( SELECT a1.*,

      (SELECT max(modificationDate)
       FROM Activity a2
       GROUP BY a2.parentActivity
       HAVING a2.parentActivity = a1.name) AS childMod,
      1 AS isParent
    FROM Activity a1
    WHERE a1.parentActivity IS NULL
    UNION SELECT a3.*,
            (SELECT max(modificationDate)
             FROM Activity a4
             GROUP BY a4.name
             HAVING a3.name = a4.name), 0 AS isParent
          FROM Activity a3
          WHERE a3.parentActivity IS NOT NULL ) AS TEMP
ORDER BY childMod DESC,
  isParent DESC,
  temp.modificationDate DESC;

Вышеупомянутое можно улучшить, удалив некоторые из неэффективных подзапросов и вместо этого используя объединенные подзапросы (временную таблицу с псевдонимом) и/или унарные самосоединения.

Результат: http://i.prntscr.com/hnTeXtQzR5_BRui3TXJFSw.png

Запрос (финальный пример игрушки версии 2)

select
  *
FROM
  (

select name, modificationDate, childMaxMod, 1 as isParent from
Activity a1 left outer join
(select ParentActivity,  max(modificationDate)  as childMaxMod from Activity group by ParentActivity) as a2
on a1.name = a2.parentActivity
WHERE a1.parentActivity IS NULL

UNION

select name, modificationDate, childMaxMod, 0 as isParent from
  Activity a1 inner join
  (select ParentActivity,  max(modificationDate)  as childMaxMod from Activity group by ParentActivity) as a2
    on a1.parentActivity = a2.parentActivity
  ) as Temp
order by childMaxMod desc, isParent desc, modificationDate desc;

Java-подход

Решение Луки довольно хорошее и представляет собой чисто функциональное решение для Java 8. В качестве альтернативы я публикую свой собственный, но он использует императивное программирование на Java.

Класс активности

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;

import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

@Builder
@Data
@AllArgsConstructor
class Activity {
    long modificationDate;
    String name;
    Activity parentActivity;
    Set<Activity> subActivities = new HashSet();
    boolean active;
    public String debugString;

    public int hashCode(){
        return Objects.hash(modificationDate, name, subActivities, active);
    }
}

Код — раздел настройки

public static void main(String[] args) {
        //SpringApplication.run(MneServiceApplication.class, args);

        ArrayList<Activity> set = new ArrayList<Activity>();
        Activity p1 = Activity.builder().modificationDate(0).name("P1").subActivities(new HashSet<>()).build();
        Activity e1 = Activity.builder().modificationDate(10).name("E1").subActivities(new HashSet<>()).build();
        Activity e1_2 = Activity.builder().modificationDate(01).name("E1").subActivities(new HashSet<>()).build();

        Activity p3 = Activity.builder().modificationDate(02).name("P3").subActivities(new HashSet<>()).build();
        Activity p2 = Activity.builder().modificationDate(01).name("P2").subActivities(new HashSet<>()).build();

        p1.getSubActivities().add(e1);
        p1.getSubActivities().add(e1_2);
        e1.setParentActivity(p1);
        e1_2.setParentActivity(p1);

        set.add(p1);
        set.add(e1);
        set.add(e1_2);

        set.add(p3);
        set.add(p2);

Код — Логика (продолжение)

        Set<Activity> actives = set.stream()
                .sorted(Comparator.comparing(Activity::getModificationDate).reversed())
                .collect(Collectors.toCollection(LinkedHashSet::new));

        HashMap<Activity, Optional> maxChildModifications = new HashMap<Activity, Optional>();

        actives = actives.stream().sorted((Activity a, Activity b) -> {

            DecimalFormat decimalFormat = new DecimalFormat("0000000000000000");

            String comparisonValue_A = getComparisonString2(maxChildModifications, a, decimalFormat);
            String comparisonValue_B = getComparisonString2(maxChildModifications, b, decimalFormat);

            a.debugString = comparisonValue_A;
            b.debugString = comparisonValue_B;

            return comparisonValue_B.compareTo(comparisonValue_A);

        }).collect(Collectors.toCollection(LinkedHashSet::new));


        for(Activity activity:actives){
            System.out.println(activity.getName() + " " +  activity.getDebugString());
        }

    }

    private static String getComparisonString2(HashMap<Activity, Optional> maxChildModificationsLookup, Activity activity, DecimalFormat decimalFormat) {

        //Is a Parent or a Child
        Activity lookupActivity = activity.getParentActivity();;

        if(lookupActivity == null){ //It is a parent
            lookupActivity = activity;
        }

        String comparisonValue_B = "";
        Long isParent_B = 0L;
        Long maxChildMod_B = 0L;

            Optional<Activity> maxChildActivityOptional_B = getMaxChildModActivity(lookupActivity, maxChildModificationsLookup);

            if(maxChildActivityOptional_B.isPresent()) {
                maxChildMod_B = maxChildActivityOptional_B.get().getModificationDate();
            }

            if(activity.getSubActivities().size() > 0){
                isParent_B = 1L;    //Or use some other comparison system than the concatenated string
            }else{
                isParent_B = 0L;
            }

            comparisonValue_B = String.valueOf( decimalFormat.format(maxChildMod_B)).concat("-").concat(String.valueOf(isParent_B));
            comparisonValue_B  = comparisonValue_B .concat("-").concat(decimalFormat.format(activity.getModificationDate()));

        return comparisonValue_B;
    }

    private static Optional<Activity> getMaxChildModActivity(Activity a, HashMap<Activity, Optional> maxChildModificationsLookup) {

        Optional<Activity> result = maxChildModificationsLookup.get(a);

        if(result!=null){
            return result;
        }

        result =  a.getSubActivities().stream().max((Activity a2, Activity b2) ->
                        {
                            return new Long(a2.getModificationDate()).compareTo(b2.getModificationDate());
                        }
                );
        maxChildModificationsLookup.put(a, result);
        return result;
    }

Результат:

P1 0000000000000010-1-0000000000000000
E1 0000000000000010-0-0000000000000010
E1 0000000000000010-0-0000000000000001
P3 0000000000000000-0-0000000000000002
P2 0000000000000000-0-0000000000000001

Очевидно, что мое решение SQL и решения Luca для Java 8 более лаконичны и элегантны, чем приведенная выше реализация Java (хотя для удобочитаемости могут потребоваться некоторые изменения форматирования).

person Menelaos    schedule 22.08.2017
comment
Я с нетерпением жду ответа Java! :D - person ; 22.08.2017
comment
Извините, но вы будете продвигать свой подход к Java сегодня? - person ; 22.08.2017
comment
@ Роман, да ... проблема, однако, довольно интересная. Глядя на него, я понял, что его нельзя решить в 5-10 банальных строчек java. Кроме того, учтите, что каждое действие имеет ссылку как на родителя, так и на дочерние элементы (круговая ссылка). Сравнение использует функцию hashcode(), которая использует дочерние элементы. Следовательно: stackoverflow.com/questions/2074149/ ... Я не верю, что вашу проблему можно решить с помощью 10-строчного примера Java с серебряной пулей. SQL намного элегантнее, но я попробую! - person Menelaos; 22.08.2017
comment
Я вижу, я мог бы использовать ваш SQL-запрос, но у меня есть некоторые проблемы с его адаптацией к моей таблице базы данных. - person ; 22.08.2017
comment
все в порядке, java-код Луки работает! вы много работали над этим ответом, должен ли я дать вам Accept ()? - person ; 22.08.2017
comment
@Romain, если код Лукаса работает правильно, все в порядке. Это выглядит довольно элегантно, и я впечатлен его решением, поэтому он должен получить его, если оно правильное. - person Menelaos; 22.08.2017
comment
Тогда большое спасибо, я не знаю, как отблагодарить вашу работу! В любом случае проголосуйте за! Хорошего дня - person ; 22.08.2017