I have the following code:
list.sort(Comparator
.comparing(ProgrammData::getEnd)
.thenComparing(ProgrammData::getStart).reversed());
My problem is that I want to have my list sorted by multiple things: 1.) Group them into future events and past events (By checking if the System.currentMilliseconds() is larger than the end timestamp) 2.) Sort future events by start ascending 3.) Sort past events by end descending
Can I do this with Java 8 Lambda or do I need another way of sorting the items?
Example:
events could look like this:
name, start, end
event1, 2022-02-220100, 2022-02-220300
event2, 2022-02-220200, 2022-02-241800
event3, 2022-02-251200, 2022-02-281500
event4, 2022-02-261600, 2022-02-262100
if now() is 2022-02-221200 So the order should be:
event3 (next item in the future)
event4 (2nd next item in the future)
event2 (Ended closer to now than event1)
event1 (Longest in the past)
Try this :
final long currentTime = System.currentTimeMillis();
list.sort((el1, el2) -> {
if (el1.equals(el2)) {
return 0;
}
boolean isEl1Future = el1.getEnd().getTime() > currentTime;
boolean isEl2Future = el2.getEnd().getTime() > currentTime;
if (isEl1Future != isEl2Future) {
return isEl1Future ? -1 : 1;
}
if (Boolean.TRUE.equals(isEl1Future)) {
return el1.getStart().before(el2.getStart()) ? -1 : 1;
}
return el1.getEnd().after(el2.getEnd()) ? -1 : 1;
});
There are multiple things to notice. First, one is the .thenComparing(...)
method, which is only taking place if the previous comparing results are equal. You can read more about its behavior in the docs.
Second, if I were you I wouldn't bother to overuse the stream if I could solve it with one simple comparator. Assuming that you are looking for a new instance of the ProgrammData
list, I wrote my code in stream style, but the Comparator can be used with the List
's sort
method.
private List<ProgrammData> sortedProgramms(List<ProgrammData> dataList) {
final LocalDateTime now = LocalDateTime.now();
return dataList.stream()
.sorted((e1, e2) -> {
if (e1.getStart().isAfter(now) && e2.getStart().isAfter(now)) {
return e1.getStart().compareTo(e2.getStart());
}
return e2.getEnd().compareTo(e1.getEnd());
})
.collect(Collectors.toList());
}
The LocalDateTime().now() is using System.currentTimeMillis() inside, if there is not a more accurate given clock.
You need to split past and future events in different lists and sort them accordingly. The final step is to join both lists.
public static void main(String[] args) {
ProgrammData programmData1 = new ProgrammData("a", LocalDateTime.now().plusDays(1),
LocalDateTime.now().plusDays(1));
ProgrammData programmData2 = new ProgrammData("b", LocalDateTime.now().plusDays(2),
LocalDateTime.now().plusDays(2));
ProgrammData programmData3 = new ProgrammData("c", LocalDateTime.now().minusDays(1),
LocalDateTime.now().minusDays(1));
ProgrammData programmData4 = new ProgrammData("c", LocalDateTime.now().minusDays(2),
LocalDateTime.now().minusDays(2));
List<ProgrammData> programmDataList = new ArrayList<>();
programmDataList.add(programmData1);
programmDataList.add(programmData2);
programmDataList.add(programmData3);
programmDataList.add(programmData4);
final List<ProgrammData> collect = programmDataList.stream().sorted(Comparator
.comparing(ProgrammData::end)).toList();
LocalDateTime localDateTime = LocalDateTime.now();
final List<ProgrammData> pastEvents = collect.stream().filter(pd -> pd.end.isBefore(localDateTime))
.sorted(Comparator
.comparing(ProgrammData::end).reversed()).toList();
final List<ProgrammData> futureEvents = collect.stream().filter(pd -> pd.end.isAfter(localDateTime)).toList();
List<ProgrammData> sortedListAsRequired = new ArrayList<>();
sortedListAsRequired.addAll(futureEvents);
sortedListAsRequired.addAll(pastEvents);
System.out.println(sortedListAsRequired);
}
static record ProgrammData(String name, LocalDateTime start, LocalDateTime end) {
}
The result is something like this:
[ProgrammData[name=a, start=2022-02-23T18:08:59.564300200, end=2022-02-23T18:08:59.568806900], ProgrammData[name=b, start=2022-02-24T18:08:59.568806900, end=2022-02-24T18:08:59.568806900], ProgrammData[name=c, start=2022-02-21T18:08:59.568806900, end=2022-02-21T18:08:59.568806900], ProgrammData[name=c, start=2022-02-20T18:08:59.568806900, end=2022-02-20T18:08:59.568806900]]
Your example is confusing. As you state in the heading event2
should be handled as if it's in the future, due it's end time (2022-02-241800
) is after now (2022-02-221200
), so the ordered elements shoud be
event2
event3
event4
event1
If that's correct, you could try something like the following:
events.sort((e1, e2) -> {
// -1: e1 and e2 in the past
// 0: e1 and e2 in distinct periods
// +1: e1 and e2 in the future
int period = Integer.signum(
Integer.signum(e1.getEnd().compareTo(now))
+ Integer.signum(e2.getEnd().compareTo(now))
);
if (period == 0) {
return -e1.getEnd().compareTo(now);
}
// > 0: e1 is after e2
// = 0: e1 is equal to e2
// < 0: e1 is before e2
int comparation = e1.getComparingDateTime(now).compareTo(
e2.getComparingDateTime(now)
);
return period * comparation;
});
Given
class ProgramData {
...
public LocalDateTime getComparingDateTime(LocalDateTime reference) {
if (reference.isAfter(end)) {
// Past
return end;
}
// Future
return start;
}
...
}