Skip to content

Commit e87927f

Browse files
authored
Enda raskere combine, unngår bruk av deprecated kode (#83)
* 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
1 parent 992ecdd commit e87927f

File tree

2 files changed

+111
-126
lines changed

2 files changed

+111
-126
lines changed

src/main/java/no/nav/fpsak/tidsserie/LocalDateTimeline.java

+108-119
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,12 @@
99
import java.util.Collections;
1010
import java.util.HashSet;
1111
import java.util.Iterator;
12-
import java.util.LinkedHashMap;
1312
import java.util.List;
14-
import java.util.Map;
1513
import java.util.NavigableMap;
1614
import java.util.NavigableSet;
15+
import java.util.NoSuchElementException;
1716
import java.util.Objects;
1817
import java.util.Optional;
19-
import java.util.Set;
2018
import java.util.TreeMap;
2119
import java.util.TreeSet;
2220
import java.util.concurrent.atomic.AtomicReference;
@@ -204,24 +202,18 @@ public <T, R> LocalDateTimeline<R> combine(final LocalDateSegment<T> other,
204202
* <p>
205203
* beholder inntil videre for å kunne teste ny mot gammel implementasjon
206204
*/
207-
@Deprecated
208205
<T, R> LocalDateTimeline<R> combineGammel(final LocalDateTimeline<T> other, final LocalDateSegmentCombinator<V, T, R> combinator,
209206
final JoinStyle combinationStyle) {
210207

211-
LocalDateTimeline<R> quickExit = combinationStyle.checkQuickExit(this, other);
212-
if (quickExit != null) {
213-
return quickExit;
214-
}
215-
216208
// Join alle intervaller
217-
final NavigableMap<LocalDateInterval, Integer> joinDatoInterval = joinLocalDateIntervals(getDatoIntervaller(),
218-
other.getDatoIntervaller());
209+
final NavigableMap<LocalDateInterval, Integer> joinDatoInterval = joinLocalDateIntervals(getLocalDateIntervals(),
210+
other.getLocalDateIntervals());
219211

220212
// filtrer ut i henhold til combinationStyle
221213
final List<LocalDateSegment<R>> combinedSegmenter = new ArrayList<>();
222214
final LocalDateTimeline<V> myTidslinje = this;
223215
joinDatoInterval.entrySet().stream()
224-
.filter(e -> combinationStyle.accept(e.getValue()))
216+
.filter(e -> combinationStyle.accept((e.getValue() & LHS) > 0, (e.getValue() & RHS) > 0))
225217
.forEachOrdered(e -> {
226218
LocalDateInterval key = e.getKey();
227219
LocalDateSegment<R> nyVerdi = combinator.combine(key, myTidslinje.getSegment(key),
@@ -238,29 +230,46 @@ <T, R> LocalDateTimeline<R> combineGammel(final LocalDateTimeline<T> other, fina
238230
* Kombinerer to tidslinjer, med angitt combinator funksjon og {@link JoinStyle}.
239231
*/
240232
public <T, R> LocalDateTimeline<R> combine(final LocalDateTimeline<T> other, final LocalDateSegmentCombinator<V, T, R> combinator, final JoinStyle combinationStyle) {
241-
242-
LocalDateTimeline<R> quickExit = combinationStyle.checkQuickExit(this, other);
243-
if (quickExit != null) {
244-
return quickExit;
233+
List<LocalDateSegment<R>> combinedSegmenter = new ArrayList<>();
234+
Iterator<LocalDateSegment<V>> lhsIterator = this.segments.iterator();
235+
Iterator<LocalDateSegment<T>> rhsIterator = other.segments.iterator();
236+
LocalDateSegment<V> lhs = lhsIterator.hasNext() ? lhsIterator.next() : null;
237+
LocalDateSegment<T> rhs = rhsIterator.hasNext() ? rhsIterator.next() : null;
238+
Iterator<LocalDate> startdatoIterator = new KnekkpunktIterator<>(this.segments, other.segments);
239+
if (!startdatoIterator.hasNext()) {
240+
return empty(); //begge input-tidslinjer var tomme
245241
}
246242

247-
NavigableSet<LocalDateInterval> intervallerIKombinertTidslinje = utedIntervallerIKombinertTidslinje(toSegments(), other.toSegments(), combinationStyle);
248-
249-
//henter alle verdier med en gang, slipper å iterere i hver tidslinje en gang pr intervall
250-
Map<LocalDateInterval, LocalDateSegment<V>> aktuelleVerdierFraDenneTidslinje = this.getSegments(intervallerIKombinertTidslinje);
251-
Map<LocalDateInterval, LocalDateSegment<T>> aktuelleVerdierFraAndreTidslinje = other.getSegments(intervallerIKombinertTidslinje);
243+
LocalDate fom = startdatoIterator.next();
244+
while (startdatoIterator.hasNext()) {
245+
lhs = spolTil(lhs, lhsIterator, fom);
246+
rhs = spolTil(rhs, rhsIterator, fom);
252247

253-
List<LocalDateSegment<R>> combinedSegmenter = new ArrayList<>();
254-
intervallerIKombinertTidslinje.stream().forEachOrdered(key -> {
255-
LocalDateSegment<R> nyVerdi = combinator.combine(key, aktuelleVerdierFraDenneTidslinje.get(key), aktuelleVerdierFraAndreTidslinje.get(key));
256-
if (nyVerdi != null) {
257-
combinedSegmenter.add(nyVerdi);
248+
boolean harLhs = lhs != null && lhs.getLocalDateInterval().contains(fom);
249+
boolean harRhs = rhs != null && rhs.getLocalDateInterval().contains(fom);
250+
LocalDate nesteFom = startdatoIterator.next();
251+
if (combinationStyle.accept(harLhs, harRhs)) {
252+
LocalDateInterval periode = new LocalDateInterval(fom, nesteFom.minusDays(1));
253+
LocalDateSegment<V> tilpassetLhsSegment = harLhs ? splittVedDelvisOverlapp(this.segmentSplitter, lhs, periode) : null;
254+
LocalDateSegment<T> tilpassetRhsSegment = harRhs ? splittVedDelvisOverlapp(other.segmentSplitter, rhs, periode) : null;
255+
LocalDateSegment<R> nyVerdi = combinator.combine(periode, tilpassetLhsSegment, tilpassetRhsSegment);
256+
if (nyVerdi != null) {
257+
combinedSegmenter.add(nyVerdi);
258+
}
258259
}
259-
});
260-
260+
fom = nesteFom;
261+
}
261262
return new LocalDateTimeline<>(combinedSegmenter);
262263
}
263264

265+
private static <X> LocalDateSegment<X> splittVedDelvisOverlapp(SegmentSplitter<X> segmentSplitter, LocalDateSegment<X> segment, LocalDateInterval ønsketIntervall) {
266+
if (segment == null || segment.getLocalDateInterval().equals(ønsketIntervall)) {
267+
return segment;
268+
}
269+
return segmentSplitter.apply(ønsketIntervall, segment);
270+
}
271+
272+
264273
/**
265274
* Fikser opp tidslinjen slik at tilgrensende intervaller med equal verdi får et sammenhengende intervall. Nyttig
266275
* for å 'redusere' tidslinjen ned til enkleste form før lagring etc.
@@ -410,31 +419,6 @@ public LocalDateSegment<V> getSegment(LocalDateInterval datoInterval) {
410419
}
411420
}
412421

413-
private Map<LocalDateInterval, LocalDateSegment<V>> getSegments(NavigableSet<LocalDateInterval> datoInterval) {
414-
if (isEmpty() || datoInterval.isEmpty()) {
415-
return Map.of();
416-
}
417-
418-
Iterator<LocalDateSegment<V>> segmentIterator = segments.iterator();
419-
LocalDateSegment<V> segment = segmentIterator.next();
420-
421-
Map<LocalDateInterval, LocalDateSegment<V>> resultat = new LinkedHashMap<>();
422-
for (LocalDateInterval datoIntervall : datoInterval) {
423-
while (segment != null && segment.getTom().isBefore(datoIntervall.getFomDato())) {
424-
segment = segmentIterator.hasNext() ? segmentIterator.next() : null;
425-
}
426-
if (segment != null && segment.getLocalDateInterval().overlaps(datoIntervall)) {
427-
if (segment.getLocalDateInterval().equals(datoIntervall)) {
428-
resultat.put(datoIntervall, segment);
429-
} else {
430-
resultat.put(datoIntervall, segmentSplitter.apply(datoIntervall, segment));
431-
}
432-
}
433-
}
434-
return resultat;
435-
}
436-
437-
438422
@Override
439423
public int hashCode() {
440424
return segments.hashCode();
@@ -610,7 +594,7 @@ public String toString() {
610594
(isEmpty() ? "0" //$NON-NLS-1$
611595
: getMinLocalDate() + ", " + getMaxLocalDate()) //$NON-NLS-1$
612596
+ " [" + size() + "]" //$NON-NLS-1$ // $NON-NLS-2$
613-
+ "> = [" + getDatoIntervaller().stream().map(String::valueOf).collect(Collectors.joining(",")) + "]" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
597+
+ "> = [" + getLocalDateIntervals().stream().map(String::valueOf).collect(Collectors.joining(",")) + "]" //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
614598
;
615599
}
616600

@@ -796,53 +780,74 @@ private NavigableMap<LocalDateInterval, Integer> joinLocalDateIntervals(Navigabl
796780
return joined;
797781
}
798782

799-
private <T> NavigableSet<LocalDateInterval> utedIntervallerIKombinertTidslinje(NavigableSet<LocalDateSegment<V>> lhsIntervaller, NavigableSet<LocalDateSegment<T>> rhsIntervaller, JoinStyle combinationStyle) {
800-
if (lhsIntervaller.isEmpty()) {
801-
return combinationStyle.accept(RHS) ? toLocalDateIntervals(rhsIntervaller) : new TreeSet<>();
802-
}
803-
if (rhsIntervaller.isEmpty()) {
804-
return combinationStyle.accept(LHS) ? toLocalDateIntervals(lhsIntervaller) : new TreeSet<>();
783+
/**
784+
* Finner alle knekkpunkter fra to tidslinjer, i sekvens.
785+
* <p>
786+
* 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
787+
* <p>
788+
* 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
789+
*/
790+
private static class KnekkpunktIterator<V, T> implements Iterator<LocalDate> {
791+
private final Iterator<LocalDateSegment<V>> lhsIterator;
792+
private final Iterator<LocalDateSegment<T>> rhsIterator;
793+
private LocalDateSegment<V> lhsSegment;
794+
private LocalDateSegment<T> rhsSegment;
795+
private LocalDate next;
796+
797+
public KnekkpunktIterator(NavigableSet<LocalDateSegment<V>> lhsIntervaller, NavigableSet<LocalDateSegment<T>> rhsIntervaller) {
798+
lhsIterator = lhsIntervaller.iterator();
799+
rhsIterator = rhsIntervaller.iterator();
800+
lhsSegment = lhsIterator.hasNext() ? lhsIterator.next() : null;
801+
rhsSegment = rhsIterator.hasNext() ? rhsIterator.next() : null;
802+
803+
next = lhsSegment != null ? lhsSegment.getFom() : null;
804+
if (rhsSegment != null && (next == null || rhsSegment.getFom().isBefore(next))) {
805+
next = rhsSegment.getFom();
806+
}
805807
}
806-
NavigableSet<LocalDateInterval> joined = new TreeSet<>();
807-
Iterator<LocalDateSegment<V>> lhsIterator = lhsIntervaller.iterator();
808-
Iterator<LocalDateSegment<T>> rhsIterator = rhsIntervaller.iterator();
809-
LocalDateSegment<V> lhs = lhsIterator.next();
810-
LocalDateSegment<T> rhs = rhsIterator.next();
811-
Set<LocalDate> startdatoKandidater = finKnekkpunkter(lhsIntervaller, rhsIntervaller);
812-
Iterator<LocalDate> startdatoIterator = startdatoKandidater.iterator();
813-
LocalDate fom = startdatoIterator.next();
814-
while (startdatoIterator.hasNext()) {
815-
lhs = spolTil(lhs, lhsIterator, fom);
816-
rhs = spolTil(rhs, rhsIterator, fom);
817808

818-
boolean lhsMatch = lhs != null && lhs.getLocalDateInterval().contains(fom);
819-
boolean rhsMatch = rhs != null && rhs.getLocalDateInterval().contains(fom);
820-
int combinedFlags = (lhsMatch ? LHS : 0) | (rhsMatch ? RHS : 0);
821-
LocalDate nesteFom = startdatoIterator.next();
822-
if (combinedFlags > 0 && combinationStyle.accept(combinedFlags)) {
823-
joined.add(new LocalDateInterval(fom, nesteFom.minusDays(1)));
809+
@Override
810+
public LocalDate next() {
811+
if (next == null) {
812+
throw new NoSuchElementException("Ikke flere verdier igjen");
824813
}
825-
fom = nesteFom;
814+
LocalDate denne = next;
815+
oppdaterNeste();
816+
return denne;
826817
}
827818

828-
return joined;
829-
}
819+
@Override
820+
public boolean hasNext() {
821+
return next != null;
822+
}
830823

831-
private static <T> NavigableSet<LocalDateInterval> toLocalDateIntervals(NavigableSet<LocalDateSegment<T>> rhsIntervaller) {
832-
return rhsIntervaller.stream().map(LocalDateSegment::getLocalDateInterval).collect(Collectors.toCollection(TreeSet::new));
833-
}
824+
private void oppdaterNeste() {
825+
while (lhsSegment != null && !lhsSegment.getTom().plusDays(1).isAfter(next)) {
826+
lhsSegment = lhsIterator.hasNext() ? lhsIterator.next() : null;
827+
}
828+
while (rhsSegment != null && !rhsSegment.getTom().plusDays(1).isAfter(next)) {
829+
rhsSegment = rhsIterator.hasNext() ? rhsIterator.next() : null;
830+
}
834831

835-
private static <V, T> Set<LocalDate> finKnekkpunkter(NavigableSet<LocalDateSegment<V>> lhsIntervaller, NavigableSet<LocalDateSegment<T>> rhsIntervaller) {
836-
Set<LocalDate> startdatoKandidater = new TreeSet<>();
837-
for (LocalDateSegment<V> intervall : lhsIntervaller) {
838-
startdatoKandidater.add(intervall.getFom());
839-
startdatoKandidater.add(intervall.getTom().plusDays(1));
832+
LocalDate forrige = next;
833+
//neste knekkpunkt kan komme fra hvilken som helst av de to tidsseriene, må sjekke begge
834+
next = oppdaterKandidatForNeste(forrige, null, lhsSegment);
835+
next = oppdaterKandidatForNeste(forrige, next, rhsSegment);
840836
}
841-
for (LocalDateSegment<T> intervall : rhsIntervaller) {
842-
startdatoKandidater.add(intervall.getFom());
843-
startdatoKandidater.add(intervall.getTom().plusDays(1));
837+
838+
private static <X> LocalDate oppdaterKandidatForNeste(LocalDate forrige, LocalDate besteKandidat, LocalDateSegment<X> segment) {
839+
if (segment != null) {
840+
LocalDate fomKandidat = segment.getFom();
841+
if (fomKandidat.isAfter(forrige) && (besteKandidat == null || fomKandidat.isBefore(besteKandidat))) {
842+
return fomKandidat;
843+
}
844+
LocalDate tomKandidat = segment.getTom().plusDays(1);
845+
if (tomKandidat.isAfter(forrige) && (besteKandidat == null || tomKandidat.isBefore(besteKandidat))) {
846+
return tomKandidat;
847+
}
848+
}
849+
return besteKandidat;
844850
}
845-
return startdatoKandidater;
846851
}
847852

848853
private static <V> LocalDateSegment<V> spolTil(LocalDateSegment<V> intervall, Iterator<LocalDateSegment<V>> iterator, LocalDate fom) {
@@ -873,49 +878,37 @@ public enum JoinStyle {
873878
*/
874879
CROSS_JOIN {
875880
@Override
876-
public boolean accept(int option) {
877-
return option > 0;
881+
public boolean accept(boolean harLhs, boolean harRhs) {
882+
return harLhs || harRhs;
878883
}
879884
},
880885
/**
881886
* kun venstre tidsserie.
882887
*/
883888
DISJOINT {
884889
@Override
885-
public boolean accept(int option) {
886-
return option == LHS;
887-
}
888-
889-
@SuppressWarnings("unchecked")
890-
@Override
891-
protected <V, T, R> LocalDateTimeline<R> checkQuickExit(LocalDateTimeline<V> lhs, LocalDateTimeline<T> rhs) {
892-
return rhs.isEmpty() || lhs.isEmpty() ? (LocalDateTimeline<R>) lhs : null;
890+
public boolean accept(boolean harLhs, boolean harRhs) {
891+
return harLhs && !harRhs;
893892
}
894893
},
895894
/**
896895
* kun dersom begge tidsserier har verdi.
897896
*/
898897
INNER_JOIN {
899898
@Override
900-
public boolean accept(int option) {
901-
return (option & LHS) == LHS && (option & RHS) == RHS;
899+
public boolean accept(boolean harLhs, boolean harRhs) {
900+
return harLhs && harRhs;
902901
}
903902

904-
@Override
905-
protected <V, T, R> LocalDateTimeline<R> checkQuickExit(LocalDateTimeline<V> lhs, LocalDateTimeline<T> rhs) {
906-
boolean skip = ((lhs.isEmpty() || rhs.isEmpty()) || !new LocalDateInterval(lhs.getMinLocalDate(), lhs.getMaxLocalDate())
907-
.overlaps(new LocalDateInterval(rhs.getMinLocalDate(), rhs.getMaxLocalDate())));
908-
return skip ? new LocalDateTimeline<R>(Collections.emptyList()) : null;
909-
}
910903
},
911904
/**
912905
* alltid venstre tidsserie (LHS), høyre (RHS) kun med verdi dersom matcher. Combinator funksjon må hensyn ta
913906
* nulls for RHS.
914907
*/
915908
LEFT_JOIN {
916909
@Override
917-
public boolean accept(int option) {
918-
return (option & LHS) == LHS;
910+
public boolean accept(boolean harLhs, boolean harRhs) {
911+
return harLhs;
919912
}
920913

921914
},
@@ -925,17 +918,13 @@ public boolean accept(int option) {
925918
*/
926919
RIGHT_JOIN {
927920
@Override
928-
public boolean accept(int option) {
929-
return (option & RHS) == RHS;
921+
public boolean accept(boolean harLhs, boolean harRhs) {
922+
return harRhs;
930923
}
931924
};
932925

933-
public abstract boolean accept(int option);
926+
public abstract boolean accept(boolean harLhs, boolean harRhs);
934927

935-
@SuppressWarnings("unused")
936-
protected <V, T, R> LocalDateTimeline<R> checkQuickExit(LocalDateTimeline<V> lhs, LocalDateTimeline<T> rhs) {
937-
return null;
938-
}
939928
}
940929

941930
/**

src/test/java/no/nav/fpsak/tidsserie/LocalDateTimelineYtelseTest.java

+3-7
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,12 @@
1212
import no.nav.fpsak.tidsserie.LocalDateTimeline.JoinStyle;
1313

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

1717
private final LocalDate today = LocalDate.now();
1818

1919
@Test
20-
public void lange_tidslinjer() throws Exception {
21-
// bruker tall til å referer relative dager til today
22-
20+
void lange_tidslinjer() {
2321
for (int i = 0; i < 3000; i++) {
2422
int antall = 1000;
2523
int antallDagerPrIntervall = 5;
@@ -37,9 +35,7 @@ public void lange_tidslinjer() throws Exception {
3735
}
3836

3937
@Test
40-
public void korte_tidslinjer() throws Exception {
41-
// bruker tall til å referer relative dager til today
42-
38+
void korte_tidslinjer() {
4339
for (int i = 0; i < 200000; i++) {
4440

4541
int antall = 10;

0 commit comments

Comments
 (0)