Skip to content

Commit

Permalink
Enda raskere combine, unngår bruk av deprecated kode (#83)
Browse files Browse the repository at this point in the history
* Enda raskere combine, unngår bruk av deprecated kode
* Endringer for å gjøre SonarCloud fornøyd
* Ytterligere forbedringer - quick exit ikke lenger nødvendig, fjernes fordi den også kan gi ClassCastException for DISJOINT ved bruk av Combinator
  • Loading branch information
tendestad authored Mar 28, 2022
1 parent 992ecdd commit e87927f
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 126 deletions.
227 changes: 108 additions & 119 deletions src/main/java/no/nav/fpsak/tidsserie/LocalDateTimeline.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.NavigableSet;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicReference;
Expand Down Expand Up @@ -204,24 +202,18 @@ public <T, R> LocalDateTimeline<R> combine(final LocalDateSegment<T> other,
* <p>
* beholder inntil videre for å kunne teste ny mot gammel implementasjon
*/
@Deprecated
<T, R> LocalDateTimeline<R> combineGammel(final LocalDateTimeline<T> other, final LocalDateSegmentCombinator<V, T, R> combinator,
final JoinStyle combinationStyle) {

LocalDateTimeline<R> quickExit = combinationStyle.checkQuickExit(this, other);
if (quickExit != null) {
return quickExit;
}

// Join alle intervaller
final NavigableMap<LocalDateInterval, Integer> joinDatoInterval = joinLocalDateIntervals(getDatoIntervaller(),
other.getDatoIntervaller());
final NavigableMap<LocalDateInterval, Integer> joinDatoInterval = joinLocalDateIntervals(getLocalDateIntervals(),
other.getLocalDateIntervals());

// filtrer ut i henhold til combinationStyle
final List<LocalDateSegment<R>> combinedSegmenter = new ArrayList<>();
final LocalDateTimeline<V> myTidslinje = this;
joinDatoInterval.entrySet().stream()
.filter(e -> combinationStyle.accept(e.getValue()))
.filter(e -> combinationStyle.accept((e.getValue() & LHS) > 0, (e.getValue() & RHS) > 0))
.forEachOrdered(e -> {
LocalDateInterval key = e.getKey();
LocalDateSegment<R> nyVerdi = combinator.combine(key, myTidslinje.getSegment(key),
Expand All @@ -238,29 +230,46 @@ <T, R> LocalDateTimeline<R> combineGammel(final LocalDateTimeline<T> other, fina
* Kombinerer to tidslinjer, med angitt combinator funksjon og {@link JoinStyle}.
*/
public <T, R> LocalDateTimeline<R> combine(final LocalDateTimeline<T> other, final LocalDateSegmentCombinator<V, T, R> combinator, final JoinStyle combinationStyle) {

LocalDateTimeline<R> quickExit = combinationStyle.checkQuickExit(this, other);
if (quickExit != null) {
return quickExit;
List<LocalDateSegment<R>> combinedSegmenter = new ArrayList<>();
Iterator<LocalDateSegment<V>> lhsIterator = this.segments.iterator();
Iterator<LocalDateSegment<T>> rhsIterator = other.segments.iterator();
LocalDateSegment<V> lhs = lhsIterator.hasNext() ? lhsIterator.next() : null;
LocalDateSegment<T> rhs = rhsIterator.hasNext() ? rhsIterator.next() : null;
Iterator<LocalDate> startdatoIterator = new KnekkpunktIterator<>(this.segments, other.segments);
if (!startdatoIterator.hasNext()) {
return empty(); //begge input-tidslinjer var tomme
}

NavigableSet<LocalDateInterval> intervallerIKombinertTidslinje = utedIntervallerIKombinertTidslinje(toSegments(), other.toSegments(), combinationStyle);

//henter alle verdier med en gang, slipper å iterere i hver tidslinje en gang pr intervall
Map<LocalDateInterval, LocalDateSegment<V>> aktuelleVerdierFraDenneTidslinje = this.getSegments(intervallerIKombinertTidslinje);
Map<LocalDateInterval, LocalDateSegment<T>> aktuelleVerdierFraAndreTidslinje = other.getSegments(intervallerIKombinertTidslinje);
LocalDate fom = startdatoIterator.next();
while (startdatoIterator.hasNext()) {
lhs = spolTil(lhs, lhsIterator, fom);
rhs = spolTil(rhs, rhsIterator, fom);

List<LocalDateSegment<R>> combinedSegmenter = new ArrayList<>();
intervallerIKombinertTidslinje.stream().forEachOrdered(key -> {
LocalDateSegment<R> nyVerdi = combinator.combine(key, aktuelleVerdierFraDenneTidslinje.get(key), aktuelleVerdierFraAndreTidslinje.get(key));
if (nyVerdi != null) {
combinedSegmenter.add(nyVerdi);
boolean harLhs = lhs != null && lhs.getLocalDateInterval().contains(fom);
boolean harRhs = rhs != null && rhs.getLocalDateInterval().contains(fom);
LocalDate nesteFom = startdatoIterator.next();
if (combinationStyle.accept(harLhs, harRhs)) {
LocalDateInterval periode = new LocalDateInterval(fom, nesteFom.minusDays(1));
LocalDateSegment<V> tilpassetLhsSegment = harLhs ? splittVedDelvisOverlapp(this.segmentSplitter, lhs, periode) : null;
LocalDateSegment<T> tilpassetRhsSegment = harRhs ? splittVedDelvisOverlapp(other.segmentSplitter, rhs, periode) : null;
LocalDateSegment<R> nyVerdi = combinator.combine(periode, tilpassetLhsSegment, tilpassetRhsSegment);
if (nyVerdi != null) {
combinedSegmenter.add(nyVerdi);
}
}
});

fom = nesteFom;
}
return new LocalDateTimeline<>(combinedSegmenter);
}

private static <X> LocalDateSegment<X> splittVedDelvisOverlapp(SegmentSplitter<X> segmentSplitter, LocalDateSegment<X> segment, LocalDateInterval ønsketIntervall) {
if (segment == null || segment.getLocalDateInterval().equals(ønsketIntervall)) {
return segment;
}
return segmentSplitter.apply(ønsketIntervall, segment);
}


/**
* Fikser opp tidslinjen slik at tilgrensende intervaller med equal verdi får et sammenhengende intervall. Nyttig
* for å 'redusere' tidslinjen ned til enkleste form før lagring etc.
Expand Down Expand Up @@ -410,31 +419,6 @@ public LocalDateSegment<V> getSegment(LocalDateInterval datoInterval) {
}
}

private Map<LocalDateInterval, LocalDateSegment<V>> getSegments(NavigableSet<LocalDateInterval> datoInterval) {
if (isEmpty() || datoInterval.isEmpty()) {
return Map.of();
}

Iterator<LocalDateSegment<V>> segmentIterator = segments.iterator();
LocalDateSegment<V> segment = segmentIterator.next();

Map<LocalDateInterval, LocalDateSegment<V>> resultat = new LinkedHashMap<>();
for (LocalDateInterval datoIntervall : datoInterval) {
while (segment != null && segment.getTom().isBefore(datoIntervall.getFomDato())) {
segment = segmentIterator.hasNext() ? segmentIterator.next() : null;
}
if (segment != null && segment.getLocalDateInterval().overlaps(datoIntervall)) {
if (segment.getLocalDateInterval().equals(datoIntervall)) {
resultat.put(datoIntervall, segment);
} else {
resultat.put(datoIntervall, segmentSplitter.apply(datoIntervall, segment));
}
}
}
return resultat;
}


@Override
public int hashCode() {
return segments.hashCode();
Expand Down Expand Up @@ -610,7 +594,7 @@ public String toString() {
(isEmpty() ? "0" //$NON-NLS-1$
: getMinLocalDate() + ", " + getMaxLocalDate()) //$NON-NLS-1$
+ " [" + size() + "]" //$NON-NLS-1$ // $NON-NLS-2$
+ "> = [" + getDatoIntervaller().stream().map(String::valueOf).collect(Collectors.joining(",")) + "]" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ "> = [" + getLocalDateIntervals().stream().map(String::valueOf).collect(Collectors.joining(",")) + "]" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
;
}

Expand Down Expand Up @@ -796,53 +780,74 @@ private NavigableMap<LocalDateInterval, Integer> joinLocalDateIntervals(Navigabl
return joined;
}

private <T> NavigableSet<LocalDateInterval> utedIntervallerIKombinertTidslinje(NavigableSet<LocalDateSegment<V>> lhsIntervaller, NavigableSet<LocalDateSegment<T>> rhsIntervaller, JoinStyle combinationStyle) {
if (lhsIntervaller.isEmpty()) {
return combinationStyle.accept(RHS) ? toLocalDateIntervals(rhsIntervaller) : new TreeSet<>();
}
if (rhsIntervaller.isEmpty()) {
return combinationStyle.accept(LHS) ? toLocalDateIntervals(lhsIntervaller) : new TreeSet<>();
/**
* Finner alle knekkpunkter fra to tidslinjer, i sekvens.
* <p>
* Knekkpunkter er 'start av et intervall' og 'dagen etter slutt av et intervall'. Sistnevnte fordi det da kan være starten på et nytt intervall
* <p>
* Tidliger implementert ved å dytte alle knekkpunkter i et TreeSet og iterere, men dette er raskere O(n) vs O(n ln n), og bruker mindre minne
*/
private static class KnekkpunktIterator<V, T> implements Iterator<LocalDate> {
private final Iterator<LocalDateSegment<V>> lhsIterator;
private final Iterator<LocalDateSegment<T>> rhsIterator;
private LocalDateSegment<V> lhsSegment;
private LocalDateSegment<T> rhsSegment;
private LocalDate next;

public KnekkpunktIterator(NavigableSet<LocalDateSegment<V>> lhsIntervaller, NavigableSet<LocalDateSegment<T>> rhsIntervaller) {
lhsIterator = lhsIntervaller.iterator();
rhsIterator = rhsIntervaller.iterator();
lhsSegment = lhsIterator.hasNext() ? lhsIterator.next() : null;
rhsSegment = rhsIterator.hasNext() ? rhsIterator.next() : null;

next = lhsSegment != null ? lhsSegment.getFom() : null;
if (rhsSegment != null && (next == null || rhsSegment.getFom().isBefore(next))) {
next = rhsSegment.getFom();
}
}
NavigableSet<LocalDateInterval> joined = new TreeSet<>();
Iterator<LocalDateSegment<V>> lhsIterator = lhsIntervaller.iterator();
Iterator<LocalDateSegment<T>> rhsIterator = rhsIntervaller.iterator();
LocalDateSegment<V> lhs = lhsIterator.next();
LocalDateSegment<T> rhs = rhsIterator.next();
Set<LocalDate> startdatoKandidater = finKnekkpunkter(lhsIntervaller, rhsIntervaller);
Iterator<LocalDate> startdatoIterator = startdatoKandidater.iterator();
LocalDate fom = startdatoIterator.next();
while (startdatoIterator.hasNext()) {
lhs = spolTil(lhs, lhsIterator, fom);
rhs = spolTil(rhs, rhsIterator, fom);

boolean lhsMatch = lhs != null && lhs.getLocalDateInterval().contains(fom);
boolean rhsMatch = rhs != null && rhs.getLocalDateInterval().contains(fom);
int combinedFlags = (lhsMatch ? LHS : 0) | (rhsMatch ? RHS : 0);
LocalDate nesteFom = startdatoIterator.next();
if (combinedFlags > 0 && combinationStyle.accept(combinedFlags)) {
joined.add(new LocalDateInterval(fom, nesteFom.minusDays(1)));
@Override
public LocalDate next() {
if (next == null) {
throw new NoSuchElementException("Ikke flere verdier igjen");
}
fom = nesteFom;
LocalDate denne = next;
oppdaterNeste();
return denne;
}

return joined;
}
@Override
public boolean hasNext() {
return next != null;
}

private static <T> NavigableSet<LocalDateInterval> toLocalDateIntervals(NavigableSet<LocalDateSegment<T>> rhsIntervaller) {
return rhsIntervaller.stream().map(LocalDateSegment::getLocalDateInterval).collect(Collectors.toCollection(TreeSet::new));
}
private void oppdaterNeste() {
while (lhsSegment != null && !lhsSegment.getTom().plusDays(1).isAfter(next)) {
lhsSegment = lhsIterator.hasNext() ? lhsIterator.next() : null;
}
while (rhsSegment != null && !rhsSegment.getTom().plusDays(1).isAfter(next)) {
rhsSegment = rhsIterator.hasNext() ? rhsIterator.next() : null;
}

private static <V, T> Set<LocalDate> finKnekkpunkter(NavigableSet<LocalDateSegment<V>> lhsIntervaller, NavigableSet<LocalDateSegment<T>> rhsIntervaller) {
Set<LocalDate> startdatoKandidater = new TreeSet<>();
for (LocalDateSegment<V> intervall : lhsIntervaller) {
startdatoKandidater.add(intervall.getFom());
startdatoKandidater.add(intervall.getTom().plusDays(1));
LocalDate forrige = next;
//neste knekkpunkt kan komme fra hvilken som helst av de to tidsseriene, må sjekke begge
next = oppdaterKandidatForNeste(forrige, null, lhsSegment);
next = oppdaterKandidatForNeste(forrige, next, rhsSegment);
}
for (LocalDateSegment<T> intervall : rhsIntervaller) {
startdatoKandidater.add(intervall.getFom());
startdatoKandidater.add(intervall.getTom().plusDays(1));

private static <X> LocalDate oppdaterKandidatForNeste(LocalDate forrige, LocalDate besteKandidat, LocalDateSegment<X> segment) {
if (segment != null) {
LocalDate fomKandidat = segment.getFom();
if (fomKandidat.isAfter(forrige) && (besteKandidat == null || fomKandidat.isBefore(besteKandidat))) {
return fomKandidat;
}
LocalDate tomKandidat = segment.getTom().plusDays(1);
if (tomKandidat.isAfter(forrige) && (besteKandidat == null || tomKandidat.isBefore(besteKandidat))) {
return tomKandidat;
}
}
return besteKandidat;
}
return startdatoKandidater;
}

private static <V> LocalDateSegment<V> spolTil(LocalDateSegment<V> intervall, Iterator<LocalDateSegment<V>> iterator, LocalDate fom) {
Expand Down Expand Up @@ -873,49 +878,37 @@ public enum JoinStyle {
*/
CROSS_JOIN {
@Override
public boolean accept(int option) {
return option > 0;
public boolean accept(boolean harLhs, boolean harRhs) {
return harLhs || harRhs;
}
},
/**
* kun venstre tidsserie.
*/
DISJOINT {
@Override
public boolean accept(int option) {
return option == LHS;
}

@SuppressWarnings("unchecked")
@Override
protected <V, T, R> LocalDateTimeline<R> checkQuickExit(LocalDateTimeline<V> lhs, LocalDateTimeline<T> rhs) {
return rhs.isEmpty() || lhs.isEmpty() ? (LocalDateTimeline<R>) lhs : null;
public boolean accept(boolean harLhs, boolean harRhs) {
return harLhs && !harRhs;
}
},
/**
* kun dersom begge tidsserier har verdi.
*/
INNER_JOIN {
@Override
public boolean accept(int option) {
return (option & LHS) == LHS && (option & RHS) == RHS;
public boolean accept(boolean harLhs, boolean harRhs) {
return harLhs && harRhs;
}

@Override
protected <V, T, R> LocalDateTimeline<R> checkQuickExit(LocalDateTimeline<V> lhs, LocalDateTimeline<T> rhs) {
boolean skip = ((lhs.isEmpty() || rhs.isEmpty()) || !new LocalDateInterval(lhs.getMinLocalDate(), lhs.getMaxLocalDate())
.overlaps(new LocalDateInterval(rhs.getMinLocalDate(), rhs.getMaxLocalDate())));
return skip ? new LocalDateTimeline<R>(Collections.emptyList()) : null;
}
},
/**
* alltid venstre tidsserie (LHS), høyre (RHS) kun med verdi dersom matcher. Combinator funksjon må hensyn ta
* nulls for RHS.
*/
LEFT_JOIN {
@Override
public boolean accept(int option) {
return (option & LHS) == LHS;
public boolean accept(boolean harLhs, boolean harRhs) {
return harLhs;
}

},
Expand All @@ -925,17 +918,13 @@ public boolean accept(int option) {
*/
RIGHT_JOIN {
@Override
public boolean accept(int option) {
return (option & RHS) == RHS;
public boolean accept(boolean harLhs, boolean harRhs) {
return harRhs;
}
};

public abstract boolean accept(int option);
public abstract boolean accept(boolean harLhs, boolean harRhs);

@SuppressWarnings("unused")
protected <V, T, R> LocalDateTimeline<R> checkQuickExit(LocalDateTimeline<V> lhs, LocalDateTimeline<T> rhs) {
return null;
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,12 @@
import no.nav.fpsak.tidsserie.LocalDateTimeline.JoinStyle;

@Disabled //kan kjøres lokalt for å få inntrykk av ytelse ved endringer på implementasjon
public class LocalDateTimelineYtelseTest {
class LocalDateTimelineYtelseTest {

private final LocalDate today = LocalDate.now();

@Test
public void lange_tidslinjer() throws Exception {
// bruker tall til å referer relative dager til today

void lange_tidslinjer() {
for (int i = 0; i < 3000; i++) {
int antall = 1000;
int antallDagerPrIntervall = 5;
Expand All @@ -37,9 +35,7 @@ public void lange_tidslinjer() throws Exception {
}

@Test
public void korte_tidslinjer() throws Exception {
// bruker tall til å referer relative dager til today

void korte_tidslinjer() {
for (int i = 0; i < 200000; i++) {

int antall = 10;
Expand Down

0 comments on commit e87927f

Please sign in to comment.