Skip to content

Commit

Permalink
feat: split localdatetimeline by period (ex. 1 year) (#32)
Browse files Browse the repository at this point in the history
  • Loading branch information
frode-carlsen authored Oct 13, 2020
1 parent e089583 commit 91f4a96
Show file tree
Hide file tree
Showing 4 changed files with 108 additions and 14 deletions.
2 changes: 1 addition & 1 deletion src/main/java/no/nav/fpsak/tidsserie/LocalDateSegment.java
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public boolean equals(Object obj) {

}

private boolean equalValues(LocalDateSegment other) {
private boolean equalValues(LocalDateSegment<?> other) {
if (value instanceof BigDecimal && other.value instanceof BigDecimal) {
// special case
return ((BigDecimal) value).compareTo((BigDecimal) other.value) == 0;
Expand Down
57 changes: 51 additions & 6 deletions src/main/java/no/nav/fpsak/tidsserie/LocalDateTimeline.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import java.io.Serializable;
import java.time.LocalDate;
import java.time.Period;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.NavigableMap;
import java.util.NavigableSet;
Expand All @@ -21,6 +23,7 @@
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
Expand All @@ -43,7 +46,7 @@
*/
@JsonSerialize(using = LocalDateTimelineFormatters.Serializer.class)
@JsonDeserialize(using = LocalDateTimelineFormatters.Deserializer.class)
public class LocalDateTimeline<V> implements Serializable {
public class LocalDateTimeline<V> implements Serializable, Iterable<LocalDateSegment<V>> {

@SuppressWarnings("rawtypes")
public static final LocalDateTimeline EMPTY_TIMELINE = new LocalDateTimeline<>(Collections.emptyList());
Expand Down Expand Up @@ -121,7 +124,7 @@ public LocalDateTimeline(Collection<LocalDateSegment<V>> datoSegmenter,
for (LocalDateSegment<V> ds : datoSegmenter) {
add(ds, overlapCombinator);
}
this.segmentSplitter = customSegmentSplitter != null ? customSegmentSplitter : new SegmentSplitter<>();
this.segmentSplitter = customSegmentSplitter != null ? customSegmentSplitter : new EqualValueSegmentSplitter<>();
validateNonOverlapping();
}

Expand Down Expand Up @@ -173,6 +176,17 @@ public void accept(LocalDateSegment<V> t) {
return new LocalDateTimeline<>(evaluator.segmenter);

}

/**
* Kombinerer denne tidsserien med et enkelt segment (convenience function)
*
* @see #combine(LocalDateTimeline, LocalDateSegmentCombinator, JoinStyle)
*/
public <T, R> LocalDateTimeline<R> combine(final LocalDateSegment<T> other,
final LocalDateSegmentCombinator<V, T, R> combinator,
final JoinStyle combinationStyle) {
return combine(new LocalDateTimeline<T>(List.of(other)), combinator, combinationStyle);
}

/**
* Kombinerer to tidslinjer, med angitt combinator funksjon og {@link JoinStyle}.
Expand Down Expand Up @@ -236,20 +250,39 @@ public LocalDateTimeline<V> compress(BiPredicate<V, V> e, LocalDateSegmentCombin
}

/**
* Returnerer timeline der enten denne eller andre har verdier.
* Returnerer timeline der enten denne eller andre (eller begge) har verdier.
*/
public <T, R> LocalDateTimeline<R> crossJoin(LocalDateTimeline<T> other, LocalDateSegmentCombinator<V, T, R> combinator) {
return combine(other, combinator, JoinStyle.CROSS_JOIN);
}

/**
* Disjoint this timeline with given interval. Returns all parts of this timeline not overlapping with given
* interval.
* interval. Can be used to cut out a given interval from this timeline.
* @return new timeline without given interval.
*/
public LocalDateTimeline<V> disjoint(LocalDateInterval datoInterval) {
LocalDateTimeline<V> intervalTimeline = new LocalDateTimeline<>(Arrays.asList(new LocalDateSegment<V>(datoInterval, null)));
return this.disjoint(intervalTimeline, StandardCombinators::leftOnly);
}

public LocalDateTimeline<V> splitAtRegular(LocalDate startDate, Period period){
LocalDate endDate = getMaxLocalDate();

// nye segmenter
List<LocalDateSegment<V>> segmenter = new ArrayList<>();

LocalDate dt = startDate;
while(!dt.isAfter(endDate)) {
LocalDate nextDt = dt.plus(period);
// trekk 1 fra nextDt siden vi har fom/tom (ikke fom /til)
var nesteSegmenter = intersection(new LocalDateInterval(dt, nextDt.minusDays(1))).toSegments();
segmenter.addAll(nesteSegmenter);
dt = nextDt;
}

return new LocalDateTimeline<>(segmenter);
}

/**
* Returnerer kun intervaller der denne timeline har verdier, men andre ikke har det.
Expand Down Expand Up @@ -456,6 +489,15 @@ public NavigableSet<LocalDateSegment<V>> toSegments() {
return Collections.unmodifiableNavigableSet(segments);
}

@Override
public Iterator<LocalDateSegment<V>> iterator() {
return toSegments().iterator();
}

public Stream<LocalDateSegment<V>> stream() {
return segments.stream();
}

/** Find timeline of unique segments in collection of segments that may overlap. */
public static<V> LocalDateTimeline<List<V>> buildGroupOverlappingSegments(Collection<LocalDateSegment<V>> segmentsWithPossibleOverlaps){
@SuppressWarnings({ "cast" })
Expand Down Expand Up @@ -753,8 +795,11 @@ public interface Reducer<V, R> {
R reduce(R aggregateValue, LocalDateSegment<V> nextSegment);
}

private static final class SegmentSplitter<V>
implements BiFunction<LocalDateInterval, LocalDateSegment<V>, LocalDateSegment<V>>, Serializable {
/** Interface for å custom funksjon for å splitte segmenter. */
public interface SegmentSplitter<V> extends BiFunction<LocalDateInterval, LocalDateSegment<V>, LocalDateSegment<V>>{
}

private static final class EqualValueSegmentSplitter<V> implements SegmentSplitter<V>, Serializable {
@Override
public LocalDateSegment<V> apply(LocalDateInterval di, LocalDateSegment<V> seg) {
if (di.equals(seg.getLocalDateInterval())) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package no.nav.fpsak.tidsserie;

import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;

import java.time.LocalDate;
Expand All @@ -12,6 +11,7 @@

public class LocalDateIntervalTest {

@SuppressWarnings("deprecation")
@Rule
public final ExpectedException expectedException = ExpectedException.none();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static org.assertj.core.api.Assertions.assertThat;

import java.time.LocalDate;
import java.time.Period;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
Expand Down Expand Up @@ -131,8 +132,7 @@ public class LocalDateTimelineExamplesTest {
{ 6, 6, List.of("B") },
}));
}



@Test
public void eksempel_slå_sammen_med_alle_verdier() throws Exception {
// bruker tall til å referer relative dager til today
Expand All @@ -150,7 +150,7 @@ public class LocalDateTimelineExamplesTest {
assertThat(timelineA.intersection(timelineB, StandardCombinators::allValues)).isEqualTo(toTimeline(new Object[][] {
{ 3, 5, List.of("A", "B") },
}));

// cartesian product (cross join) med Alle Verdier: A ∪ B
assertThat(timelineA.combine(timelineB, StandardCombinators::allValues, JoinStyle.CROSS_JOIN)).isEqualTo(toTimeline(new Object[][] {
{ 0, 2, Arrays.asList("A") },
Expand Down Expand Up @@ -201,7 +201,7 @@ public void eksempel_kun_venstre_side() throws Exception {
{ 3, 5, List.of("A") },
}));
}

@Test
public void eksempel_kun_høyre_side() throws Exception {
// bruker tall til å referer relative dager til today
Expand All @@ -220,7 +220,7 @@ public void eksempel_kun_venstre_side() throws Exception {
{ 3, 5, "B" },
}));
}

@Test
public void eksempel_slå_sammen_tall_verdier() throws Exception {
// bruker tall til å referer relative dager til today
Expand Down Expand Up @@ -277,7 +277,7 @@ public void eksempel_kun_venstre_side() throws Exception {
{ 6, 6, B },
}));
}

@Test
public void eksempel_multipliser_sammen_tall_verdier() throws Exception {
// bruker tall til å referer relative dager til today
Expand Down Expand Up @@ -394,4 +394,53 @@ private <V> LocalDateTimeline<V> toTimeline(Object[][] data) {

return new LocalDateTimeline<>(segments);
}

@Test
public void eksempel_splitt_av_tidsserie_ved_period_year() throws Exception {

var timeline = new LocalDateTimeline<>(
List.of(
toSegment("2019-12-01", "2020-12-31", "A"),
toSegment("2017-01-01", "2017-12-31", "B")));

var mappedTimeline = timeline.splitAtRegular(LocalDate.parse("2016-01-01"), Period.ofYears(1));

var expectedTimeline = new LocalDateTimeline<>(
List.of(
toSegment("2019-12-01", "2019-12-31", "A"),
toSegment("2020-01-01", "2020-12-31", "A"),
toSegment("2017-01-01", "2017-12-31", "B")));

assertThat(mappedTimeline).isEqualTo(expectedTimeline);
}

@Test
public void eksempel_splitt_av_tidsserie_ved_period_day_3() throws Exception {

var timeline = new LocalDateTimeline<>(
List.of(
toSegment("2019-12-01", "2020-01-02", "A")));

var mappedTimeline = timeline.splitAtRegular(LocalDate.parse("2019-12-01"), Period.ofDays(3));

var expectedTimeline = new LocalDateTimeline<>(
List.of(
toSegment("2019-12-01", "2019-12-03", "A"),
toSegment("2019-12-04", "2019-12-06", "A"),
toSegment("2019-12-07", "2019-12-09", "A"),
toSegment("2019-12-10", "2019-12-12", "A"),
toSegment("2019-12-13", "2019-12-15", "A"),
toSegment("2019-12-16", "2019-12-18", "A"),
toSegment("2019-12-19", "2019-12-21", "A"),
toSegment("2019-12-22", "2019-12-24", "A"),
toSegment("2019-12-25", "2019-12-27", "A"),
toSegment("2019-12-28", "2019-12-30", "A"),
toSegment("2019-12-31", "2020-01-02", "A")));

assertThat(mappedTimeline).isEqualTo(expectedTimeline);
}

private static <V> LocalDateSegment<V> toSegment(String dt1, String dt2, V val) {
return new LocalDateSegment<>(LocalDate.parse(dt1), LocalDate.parse(dt2), val);
}
}

0 comments on commit 91f4a96

Please sign in to comment.