2017-09-12 55 views
8

Jak przerwać obliczanie strumienia na podstawie poprzednich wyników? Jeśli jest oczywiste, że stream.filter (...). Count() będzie mniejszy niż pewna liczba - jak zatrzymać obliczanie strumienia?Zatrzymywanie obliczeń strumienia java na podstawie wcześniejszych wyników obliczeń

Mam następujący kod, który sprawdza, czy niektóre sampleData przechodzi test predicate:

// sampleData.size() may be greater than 10.000.000 
Set<String> sampleData = downloadFromWeb(); 
return sampleData.stream().filter(predicate::test).count() > sampleData.size() * coefficient; 

mogłem tysiące sampleData. Problem polega na tym, że ten kod jest nieskuteczny. Na przykład, jeśli coefficient równa 0.5, sampleData.size() = 10_000_000, a pierwsze elementy 5_000_000 awarii predicate::test - nie ma powodu, aby potwierdzić ostatnie 5_000_000 elementy (count() nigdy nie będzie większy niż 5_000_000).

+2

Dlaczego po prostu użyć pętli for? Java 9 zapewni metodę Stream :: takeWhile, która powinna zrobić to, czego potrzebujesz. – daniu

+0

Chcesz przywrócić wartość true, gdy połowa + 1 ma wartość true AND false, jeśli połowa + 1 ma wartość false? – ByeBye

+0

@ ByeBye Chcę zwrócić dokładną liczbę, jeśli jest większa niż połowa, i zero lub coś innego, jeśli liczba jest mniejsza niż połowa –

Odpowiedz

4

ZhekaKozlov’s answer zmierza w dobrym kierunku, ale brakuje mu negacji. Aby dopasowania były większe niż określony próg, liczba elementów niezgodnych musi być mniejsza niż "próg wielkości". Jeśli testujemy dla niepasujących elementów będzie mniejszy, możemy zastosować limit przestać raz stają się większe:

Set<String> sampleData = downloadFromWeb(); 
final long threshold = sampleData.size()-(long)(sampleData.size() * coefficient); 
return sampleData.stream() 
       .filter(predicate.negate()).limit(threshold+1).count() < threshold; 

Tam jest, nawiasem mówiąc, nie ma powodu, aby utworzyć odwołanie metoda metody testowej istniejący Predicate jak z predicate::test. Wystarczy przekazać Predicate do metody filter. Powyższy kod również używa predicate.negate() zamiast predicate.negate()::test ...

+0

Niestety, to rozwiązanie nie jest zwarciem. Na przykład. jeśli chcesz przetestować, że Set.of (1,2,3,4,5,6,7,8,9,10) zawiera 3 elementy mniejsze niż 4, przetestuje wszystkie 10 elementów, ale musisz tylko przetestować pierwsze 3 elementy. To rozwiązanie jest przeciwieństwem mojego. – ZhekaKozlov

+0

@ZhekaKozlov: ponieważ 'Set' nie ma zdefiniowanej kolejności, nie ma żadnych" pierwszych "trzech elementów, ale w każdym razie twój kod ma zwarcie po napotkaniu wystarczającej liczby pasujących elementów, mój robi zwarcie, gdy napotkał wystarczająco dużo pasujące elementy. Obaj mają rację, ale mój jest tym, o co OP poprosił ... – Holger

+0

Oczywiście, OP chce zwarcia w obu kierunkach. – ZhekaKozlov

0
Set<String> sampleData = downloadFromWeb(); 
int size = (int) (sampleData.size() * coefficient); 
return sampleData.stream().filter(predicate::test).limit(size + 1).count() > size; 
+0

Zastanowiłem się nad tym, ale mimo wszystko, to nie będzie cięte w przeciwny sposób. – ByeBye

+1

'size + 1' jest tylko dobrym limitem, jeśli wszystkie pierwsze elementy' size' zawiodą predykat. Limit 5.000.000 w pytaniu to tylko przykład. –

+0

@ByeBye Dobra rada, ale obawiam się, że wymagałoby 'takeWhile' dla zwięzłego rozwiązania. – ZhekaKozlov

4

Szczerze mówiąc nie jestem całkiem pewien, że to byłoby poprawne, mam nadzieję, że ktoś przyjdzie i recenzję, ale tutaj jest mój pomysł wykorzystania niestandardowych spliterator:

static class CustomSpl<T> extends AbstractSpliterator<T> { 

    private Spliterator<T> source; 

    private int howMany; 

    private int coefficient; 

    private Predicate<T> predicate; 

    private T current; 

    private long initialSize; 

    private void setT(T t) { 
     this.current = t; 
    } 

    public CustomSpl(Spliterator<T> source, int howMany, int coefficient, Predicate<T> predicate, long initialSize) { 
     super(source.estimateSize(), source.characteristics()); 
     this.source = source; 
     this.howMany = howMany; 
     this.coefficient = coefficient; 
     this.predicate = predicate; 
     this.initialSize = initialSize; 
    } 

    @Override 
    public boolean tryAdvance(Consumer<? super T> action) { 
     boolean hasMore = source.tryAdvance(this::setT); 

     System.out.println(current); 

     if (!hasMore) { 
      return false; 
     } 

     if (predicate.test(current)) { 
      ++howMany; 
     } 

     if (initialSize - howMany <= coefficient) { 
      return false; 
     } 

     action.accept(current); 
     return true; 
    } 

} 

a dla Przykład tego będzie produkować tylko 4 elementy, ponieważ mówi się, zależy tylko o współczynniku 5:

Spliterator<Integer> sp = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).stream().spliterator(); 

long count = StreamSupport.stream(new CustomSpl<>(sp, 0, 5, x -> x > 3, sp.getExactSizeIfKnown()), false) 
      .count(); 

Ponadto jest możliwe spliterators z tylko znanej wielkości.

+1

" Współczynnik "prawdopodobnie wynosi od 0,0 do 1,0. –

+1

@JohnKugelman tak, jest to właściwie współczynnik 'sampleData.size() *. Nie mogłem wymyślić lepszego imienia ... – Eugene

+0

!!! Myślę, że jedynym sposobem jest stworzenie niestandardowego spliteratora obsługującego funkcje zwarciowe. –