Wielokrotne wykorzystanie wyniku długich obliczeń w strumieniu

0

Mam pewną kolekcję, którą chciałbym przefiltrować, a potem wyznaczyć element spełniający jakąś minimalną wartość:

myCollection,stream().filter(p).min(Comparator.comparing(v));

W powyższym p jest pewnym predykatem, a v obiektem funkcyjnym zwracającym wartość typu mającego zdefiniowaną relację porównania (np. double).
Co jeśli p i v wykonują pod spodem te same obliczenia? Czy da się wykonać je tylko raz, a potem wykorzystać w kilku miejscach? Np. czy w takim przykładzie:

myCollection,stream()
.filter(element -> longRunningCalculationScore(element) < Double.POSITIVE_INFINITY)
.min(Comparator.comparing(element -> longRunningCalculationScore(element)));

element -> longRunningCalculationScore(element) będzie wykonany raz i wykorzystany w obu miejscach, czy wykona się go dwukrotnie?

1

Przy takiej implementacji będzie wykonany dwa razy.
Żeby temu zapobiec możesz sobie zwrócić krotkę:

myCollection.stream()
.map(element -> Tuple.of(element,  longRunningCalculationScore(element)))
.filter(tuple -> tuple.getLongRunningCalculationScore() < Double.POSITIVE_INFINITY)
.min(Comparator.comparing(tuple-> tuple.getLongRunningCalculationScore()))
.getElement();
2

Może wykonać się wiele więcej niż 2 razy: dla każdego elementu w filter, a potem w porównywaniu.

https://www.vavr.io/vavr-docs/#_memoization

Function1<Element, Double> memoizedCalculation = Function1.of(this::longRunningCalculationScore).memoized();

i wtedy

myCollection,stream()
.filter(element -> memoizedCalculation.apply(element) < Double.POSITIVE_INFINITY)
.min(Comparator.comparing(element -> memoizedCalculation.apply(element)));

właściwe obliczenia wykonają się tyle razy, ile jest różnych elementów w kolekcji na początku.

0

Dzięki za odpowiedzi.
Jak rozumiem obie propozycje są dość równoważne - w jednym Tuple<element,wynik>, w drugim obiekt funkcyjny z tymi samymi parametrami. Różnica polega tylko na tym, gdzie wywołamy obliczenia?

@Potat0x, nie do końca zrozumiałem czemu napisałeś, że obliczenia miałyby się wykonać więcej niż 2 razy? Oczywiście pisząc, że wykonają się 2 razy miałem na myśli "2 razy dla każdego elementu w kolekcji". Czy mój pierwotny zapis mógł spowodować wykonanie obliczeń ponad 2 razy dla każdego elementu?

0
GutekSan napisał(a):

Dzięki za odpowiedzi.
Jak rozumiem obie propozycje są dość równoważne - w jednym Tuple<element,wynik>, w drugim obiekt funkcyjny z tymi samymi parametrami. Różnica polega tylko na tym, gdzie wywołamy obliczenia?

Mniej więcej tak (przy sposobie z memoized obliczenia nie będą powielane dla tych samych elementów wejściowych, a przy początkowym mapowaniu na tuple może się to zdarzać).

@Potat0x, nie do końca zrozumiałem czemu napisałeś, że obliczenia miałyby się wykonać więcej niż 2 razy? Oczywiście pisząc, że wykonają się 2 razy miałem na myśli "2 razy dla każdego elementu w kolekcji".

Jeżeli to miałeś na myśli to oczywiście masz rację :P

//edit: jednak nie do końca.

Czy mój pierwotny zapis mógł spowodować wykonanie obliczeń ponad 2 razy dla każdego elementu?

Wewnątrz .min(Comparator.comparing(element -> longRunningCalculationScore(element))) będzie około N porównań. Dla każdego z tych porównań metoda longRunningCalculationScore zostanie wykonana 2 razy. Czyli w sumie 2N wywołań wewnątrz min.

1 użytkowników online, w tym zalogowanych: 0, gości: 1