12

I have an entity Activity that can be a parent or child.

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

I have to sort the activities previously loaded from the database by their modification date, but still grouped by their parent activity.

Rules: A child E1 activity can be updated while its parent P1 isn't. So the modificationDate of E1 can be greater than P1.
If a child is updated, its parent has to rise top of list with all its childs

Example: activity | modification date

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

After sorting, it should give

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

Java base

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));

I don't really know if it could be performed by a sql request, hope someone could provide me the solution.

EDIT:

It's not a school project. I'm making a rest service that provides database models. Proff for ppl thinking i'm a liar: link

At this moment, a get request on http://localhost/activity provides a set activities unsorted, as json. My website calling this webservice also have unsorted activities, and the activity table is totally unreadable.

13
  • 3
    I'm a trainee, I'm just looking for a solution after 2 days of headlock, I make a rest service :[ Commented Aug 22, 2017 at 13:00
  • 2
    looking for a world without haters, I don't think my subject dispesrect the stackoverflow rules, it took me time to make this subject Commented Aug 22, 2017 at 13:04
  • 1
    Write a Comparator which will sort the list according to the requested value for you. Comparator Commented Aug 22, 2017 at 13:04
  • 2
    Here's a tip, forget a second about streams etc... go back to basics. How, would you implement it when you use a couple of for loops and recursion. Then when you have a working set, you can see how you can optimize it with streams, but I'd only suggest that if you have datasets larger than 1000 to sort through. Commented Aug 22, 2017 at 13:08
  • 1
    this looks like homework and is worded like a homework problem. You are not writing in the first person. It's like you are copying the homework assignment and it is worded like that. What have YOU tried? Why isn't it working? What are your specific problems? Commented Aug 22, 2017 at 13:13

2 Answers 2

9

So my idea is the following. Since the parent activity contains a set of all children we can use this to get a list of parents sorted by the most recently updated child.

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));

Now create a List where the result will be stored.

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);

You will obtain a List of activities as you wanted, first is the parent, then it's children sorted by updateDate, etc.

UPDATE

In case the tree has multiple levels, you will have to obtain all its children recursively, then sort them.

Method of obtaining all the children of an 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);
    });
}

And use it like this :

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

Do the same when you collect all the results inside the stream.

Sign up to request clarification or add additional context in comments.

18 Comments

GetModificationDate() is an integer, not a string D:
@Romain, oh, I treated it as a Date
Thanks :D. Still, isn't it preferable to call flatMap() instead of get() for the optional? It may produce an exception
Ye after testing, got java.util.NoSuchElementException: No value present
@Hulk, in this list I'm also adding the parent finalResult.add(set);, So a simple collect won't work. There is a way of collecting the parent and it's sorted children, can't figure it out though :(
|
5

Intro

I will start off using an SQL Approach, and follow on later with Java. (SQL is my love).

SQL Approach

I don't really know if it could be performed by a sql request, hope someone could provide me the solution.

This could be solved using an SQL approach. Here is a first non-optimized example:

Schema

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' );

Query (Version 1 Toy Example)

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;

The above can be improved by removing some of the inefficient sub queriess and instead using joined sub queries (aliased temp table) and/or unary self joins.

Result: http://i.prntscr.com/hnTeXtQzR5_BRui3TXJFSw.png

Query (Final Version 2 Toy Example)

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 Approach

Luca's solution is quite good and is purely a java 8 functional solution. I post my own as an alternative but it makes use of imperative java programming.

Activity Class

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);
    }
}

Code - Setup Section

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);

Code - Logic (Continuation)

        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;
    }

Result:

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

Clearly my SQL solution, and Luca's Java 8 solutions are more more concise and elegant than the above java implementation (though some formatting changes might be needed for readability).

7 Comments

I am looking forward to the java answer! :D
sorry but will you push your java approach today?
@Roman yeh... the problem however is quite interesting. I realized by looking at it that it cannot be solved in 5-10 trivial lines of java. Also, consider that each Activity has a reference to both the parent that has a reference to the children (Circular reference). Comparison uses hashcode() function that uses children. Hence: stackoverflow.com/questions/2074149/… ... I don't believe your problem can be solved with a "silver-bullet" 10-line java example. The SQL is much more elegant but I will give it a shot!
I see, I could use your sql query but I have some troubles to adapt it to my database table
it's okay, Luca's java code works ! you worked a lot for this answer, should I give you the Accept() ?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.