I have a situation like this:
List<ObjectA> collapsed = list.stream().collect(Collectors.collectingAndThen(
Collectors.groupingBy(
ObjectA::getDate,
Collectors.maxBy(Comparator
.comparing(ObjectA::getPriority1)
.thenComparing(ObjectA::getPriority2)
.thenComparing(ObjectA::getDate)
.thenComparing(ObjectA::getId))),
map -> map.values().stream()
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList())));
and it works perfectly for the same dates. I just need to group items for a certain date window:
Integer daysWindow = 90;
so i want everything to happen for the dates in this window. how would this be possible?
Thanks to all!
CodePudding user response:
You can apply the filter in the beginning as shown below keeping the rest of the things the same.
LocalDate today = LocalDate.now();
list.stream().filter(obj -> !obj.getDate().isAfter(today.plusDays(90)))
.collect(Collectors.collectingAndThen(....
Note: I assume ObjectA::getDate
is of type java.time.LocalDate
.
Demo:
import java.time.LocalDate;
public class Main {
public static void main(String[] args) {
// Test
System.out.println(inRange(LocalDate.now()));
System.out.println(inRange(LocalDate.of(2021, 12, 31)));
System.out.println(inRange(LocalDate.of(2022, 2, 20)));
}
static boolean inRange(LocalDate date) {
return !date.isAfter(LocalDate.now().plusDays(90));
}
}
Output:
true
false
false
Learn more about the modern Date-Time API* from Trail: Date Time.
* For any reason, if you have to stick to Java 6 or Java 7, you can use ThreeTen-Backport which backports most of the java.time functionality to Java 6 & 7. If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8 APIs available through desugaring and How to use ThreeTenABP in Android Project.
CodePudding user response:
As another approach, if what you're looking for is a map of date ranges to the corresponding values, you can give it a bit more work by creating a range data type, then generating groups on that type. Here's an example:
class DateRange {
private final LocalDate from;
private final int length;
public DateRange(LocalDate from, int length) {
super();
this.from = from;
this.length = length;
}
//getters
@Override
public int hashCode() {
return this.from.hashCode(); //TODO
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof DateRange))
return false;
DateRange r = (DateRange) obj;
return this.length == r.length && this.from.equals(r.from);
}
@Override
public String toString() {
return "(" from " to " from.plusDays(length) ")";
}
}
Then your stream can create a mapping, just as the one you currently have, but grouping on date ranges, after determining the range under which each element falls.
int daysWindow = 60;
var start = LocalDate.of(2021, 1, 1);
//example date list
List<LocalDate> dates = List.of(start.plusDays(20),
start.plusDays(30), start.plusDays(40), start.plusDays(50),
start.plusDays(60), start.plusDays(70), start.plusDays(80));
var ranges = dates.stream()
.collect(Collectors.groupingBy(d -> {
var diff = ChronoUnit.DAYS.between(start, d);
return new DateRange(start.plusDays(diff - diff % daysWindow), daysWindow);
}));
Of course, you'll use your current collector, I just wanted to show the call to groupingBy
, which you can still invoke with a downstream collector.
The one thing you need to determine is the value of start
. I assume this can be set to the min date in the collection, although it can also be user-defined.
The output of the above code is as follows:
{(2021-03-02 to 2021-05-01)=[2021-03-02, 2021-03-12, 2021-03-22],
(2021-01-01 to 2021-03-02)=[2021-01-21, 2021-01-31, 2021-02-10, 2021-02-20]}
And when tested with a different window, int daysWindow = 90;
:
{(2021-01-01 to 2021-04-01)=[2021-01-21, 2021-01-31, 2021-02-10,
2021-02-20, 2021-03-02, 2021-03-12, 2021-03-22]}