2014-04-07 9 views
21

In einer vorherigen Frage [How to dynamically do filtering in Java 8?] gab Stuart Marks eine wundervolle Antwort und stellte mehrere nützliche Dienstprogramme zur Verfügung, um die Auswahl von topN und topPercent aus dem Stream zu behandeln.Wie erhalten Sie eine Reihe von Elementen aus Stream mit Java 8 Lambda?

Ich werde sie hier von seiner ursprünglichen Antwort enthalten:

@FunctionalInterface 
public interface Criterion { 
    Stream<Widget> apply(Stream<Widget> s); 
} 

Criterion topN(Comparator<Widget> cmp, long n) { 
    return stream -> stream.sorted(cmp).limit(n); 
} 

Criterion topPercent(Comparator<Widget> cmp, double pct) { 
    return stream -> { 
     List<Widget> temp = 
      stream.sorted(cmp).collect(toList()); 
     return temp.stream() 
        .limit((long)(temp.size() * pct)); 
    }; 
} 

Meine Frage lautet hier:

[1] Wie oben Artikel 3 bis 7 aus einem Strom mit bestimmtem Betrag erhalten von Artikel, so dass, wenn der Strom hat Elemente von A1, A2 .. A10, der Aufruf von

topNFromRange(Comparator<Widget> cmp, long from, long to) = topNFromRange(comparing(Widget::length), 3L, 7L) 

kehrt {A3, A4, A5, A6, A7}

Die einfachste Art, an die ich denken kann, ist, die Top 7 [T7] vom Original zu bekommen, die Top 3 [T3] vom Original zu bekommen und dann T7 - ​​T3 zu bekommen.

[2] Wie oben Artikel von oben 10% bis 30% erhält aus einem Strom mit bestimmten Menge von Gegenständen nach oben, so dass, wenn der Strom hat Elemente aus X1, X2 .. X100, der Aufruf an

topPercentFromRange(Comparator<Widget> cmp, double from, double to) = topNFromRange(comparing(Widget::length), 0.10, 0.30) 

kehren {X10, X11, X12, ..., X29, X30}

der einfachste Weg, die ich denken kann, ist das Top-30% [TP30] aus original erhalten, das besten 10% erhalten [TP10 ] vom Original und dann TP30 - TP10.

Was sind einige bessere Möglichkeiten, Java 8 Lambda zu verwenden, um die obigen Situationen präzise auszudrücken?

Antwort

19

Benutzer skiwi bereits answered der erste Teil der Frage.Der zweite Teil ist:

(2) Wie oben Artikel von oben 10% bis 30% erhält aus einem Strom nach oben mit gewissen Artikeln ....

Um dies zu tun, werden Sie muss eine ähnliche Technik wie topPercent in meiner answer zu der anderen Frage verwenden. Das heißt, Sie müssen die Elemente in einer Liste sammeln, um die Anzahl der Elemente zählen zu können, möglicherweise nachdem eine Upstream-Filterung durchgeführt wurde.

Sobald Sie die Anzahl haben, berechnen Sie die richtigen Werte für skip und limit basierend auf der Anzahl und den Prozentsätzen, die Sie möchten. So etwas wie dies funktionieren könnte:

Criterion topPercentFromRange(Comparator<Widget> cmp, double from, double to) { 
    return stream -> { 
     List<Widget> temp = 
      stream.sorted(cmp).collect(toList()); 
     return temp.stream() 
        .skip((long)(temp.size() * from)) 
        .limit((long)(temp.size() * (to - from))); 
    }; 
} 

Natürlich werden Sie die Fehlerprüfung auf from und to zu tun haben. Ein subtileres Problem besteht darin, zu bestimmen, wie viele Elemente ausgegeben werden sollen. Zum Beispiel, wenn Sie zehn Elemente haben, sind sie bei Indizes [0..9], die 0%, 10%, 20%, ..., 90% entsprechen. Aber wenn Sie nach einer Bandbreite von 9% bis 11% fragen würden, würde der obige Code überhaupt keine Elemente ausgeben, nicht die 10%, wie Sie vielleicht erwarten. Es ist also wahrscheinlich notwendig, an den Prozentberechnungen herumzubasteln, um die Semantik dessen, was Sie versuchen, zu erreichen.

+0

Nah genug zu dem, was ich suchte, werde ich die Details erarbeiten, danke! – Frank

+0

Ich habe meine Antwort aktualisiert, um auch eine Form von dem, was Sie tun, aber dann mit Collectors, vielleicht könnte es auch für die ursprüngliche Frage der Kriterien interessant sein? – skiwi

+0

@skiwi Interessant, die Finisher-Funktion eines Collectors zu verwenden, um die Sammlung wieder in einen Stream zu verwandeln. Ich bin mir nicht sicher, ob es unbedingt besser oder schlechter ist, als nur eine lokale Variable zu deklarieren. (Der Lambda-Parameter wird in diesem Fall wie ein Local verwendet.) Es ist jedoch eine nützliche Technik, die man sich für die Zukunft merken sollte. –

21

Um einen Bereich von einem Stream<T> zu erhalten, können Sie skip(long n) verwenden, um zuerst eine bestimmte Anzahl von Elementen zu überspringen, und dann können Sie limit(long n) nur nehmen eine bestimmte Menge von Elementen nennen.

einen Strom mit 10 Elementen Betrachten wir dann Elemente erhalten 3 bis 7, würden Sie normalerweise von einem List nennen:

list.subList(3, 7); 

Jetzt mit einem Stream, müssen Sie zuerst 3 Artikel überspringen, und dann nehmen 7-3 = 4 Stück, so wird es:

stream.skip(3).limit(4); 

Als Variante zu @StuartMarks' Lösung für die zweite Antwort, ich werde Ihnen die folgende Lösung anbieten, die die Möglichkeit Kette intakt lässt, funktioniert es ähnlich wie @StuartMarks es macht:

private <T> Collector<T, ?, Stream<T>> topPercentFromRangeCollector(Comparator<T> comparator, double from, double to) { 
    return Collectors.collectingAndThen(
     Collectors.toList(), 
     list -> list.stream() 
      .sorted(comparator) 
      .skip((long)(list.size() * from)) 
      .limit((long)(list.size() * (to - from))) 
    ); 
} 

und

IntStream.range(0, 100) 
     .boxed() 
     .collect(topPercentFromRangeCollector(Comparator.comparingInt(i -> i), 0.1d, 0.3d)) 
     .forEach(System.out::println); 

Dies wird die Elemente 10 bis 29

Es funktioniert durch ein Collector<T, ?, Stream<T>> verwenden, die in Ihren Elementen aus dem Strom nimmt drucken, verwandelt sie in ein List<T>, dann erhält eine Stream<T>, sortiert sie und wendet die (korrekten) Grenzen darauf an.

+0

Wenn Sie die ersten 10% der Artikel überspringen, dann sind nur noch 90% Artikel im Stream, wie erhalten Sie die Artikel von den ursprünglichen 30%, weil die 30% von den 90% nicht die ursprünglichen 30% sind , hab ich recht ? – Frank

+1

@Frank Sie müssen diese Zahlen im Voraus berechnen. – skiwi

+0

Alles klar, danke! – Frank