2017-01-23 5 views
12

Gibt es einen kurzen Weg, um sowohl den Min- als auch den Max-Wert eines Streams (basierend auf einem Komparator) in einem Durchgang zu extrahieren?Kurzer Weg, um sowohl den Min- als auch den Max-Wert von Java 8 zu erhalten

Es erscheinen viele Möglichkeiten, um die minimalen und maximalen Werte individuell zu bekommen, oder ich kann den Strom in ein temporäres Objekt sortieren, zum Beispiel:

List<T> sorted = Stream.of(...).sorted().collect(Collectors.toList()); 
T min = sorted.get(0); 
T max = sorted.get(sorted.size() - 1); 

Aber das ist nicht präzise und erfordert eine Zuweisung temporäres Objekt Ich würde lieber kein temporäres Objekt zuweisen oder zwei Durchläufe durch den Stream machen. Gibt es eine Alternative?

Pair<T> extent = Stream.of(...).??? 
+9

Haben Sie darüber nachgedacht einen Sammler wie [IntSummaryStatistics] (https://docs.oracle.com/javase/8/docs/api/java/util/IntSummaryStatistics.html)? Sie können dem Muster folgen, vorausgesetzt, es handelt sich nicht um Zahlen. –

Antwort

13

Wenn dies ein häufig benötigtes Feature ist, besser wir ein Collector machen die Arbeit zu erledigen. Wir benötigen eine Stats Klasse, die count, min, max und Factory-Methoden zum Erstellen von Statistik-Collectors enthält.

Stats<String> stats = stringStream.collect(Stats.collector()) 

fooStream.collect(Stats.collector(fooComparator)) 

(vielleicht eine bessere bequeme Methode wäre Stats.collect(stream))

ich ein Beispiel Stats Klasse gemacht -

https://gist.github.com/zhong-j-yu/ac5028573c986f7820b25ea2e74ed672

public class Stats<T> 
{ 
    int count; 

    final Comparator<? super T> comparator; 
    T min; 
    T max; 

    public Stats(Comparator<? super T> comparator) 
    { 
     this.comparator = comparator; 
    } 

    public int count(){ return count; } 

    public T min(){ return min; } 
    public T max(){ return max; } 

    public void accept(T val) 
    { 
     if(count==0) 
      min = max = val; 
     else if(comparator.compare(val, min)<0) 
      min = val; 
     else if(comparator.compare(val, max)>0) 
      max = val; 

     count++; 
    } 

    public Stats<T> combine(Stats<T> that) 
    { 
     if(this.count==0) return that; 
     if(that.count==0) return this; 

     this.count += that.count; 
     if(comparator.compare(that.min, this.min)<0) 
      this.min = that.min; 
     if(comparator.compare(that.max, this.max)>0) 
      this.max = that.max; 

     return this; 
    } 

    public static <T> Collector<T, Stats<T>, Stats<T>> collector(Comparator<? super T> comparator) 
    { 
     return Collector.of(
      ()->new Stats<>(comparator), 
      Stats::accept, 
      Stats::combine, 
      Collector.Characteristics.UNORDERED, Collector.Characteristics.IDENTITY_FINISH 
     ); 
    } 

    public static <T extends Comparable<? super T>> Collector<T, Stats<T>, Stats<T>> collector() 
    { 
     return collector(Comparator.naturalOrder()); 
    } 
} 
+1

Ich würde nicht 'UNORDERT' angeben, da dieser Sammler in der Lage ist, die Reihenfolge der Begegnungen zu respektieren, dh das erste der maximalen/minimalen Elemente zu liefern, wenn eine Krawatte genau wie 'max (...)' und 'min (...) 'tu. – Holger

+0

'IntSummaryStatistics' ist besser –

4

Ordnen Sie jedes Element des Datenstroms einem Paar zu, wobei die beiden Elemente das Minimum und das Maximum darstellen; und reduzieren Sie dann die Paare, indem Sie die Min der Minuten und die Max der Maxes nehmen.

Zum Beispiel mit etwas Pair Klasse und einige Comparator<T>:

Comparator<T> comparator = ...; 
Optional<Pair<T, T>> minMax = list.stream() 
    .map(i -> Pair.of(i /* "min" */, i /* "max" */)) 
    .reduce((a, b) -> Pair.of(
     // The min of the min elements. 
     comparator.compare(a.first, b.first) < 0 ? a.first : b.first, 
     // The max of the max elements. 
     comparator.compare(a.second, b.second) > 0 ? a.second : b.second)); 
+0

Nicht ganz so prägnant wie ich gehofft habe, aber das sieht gut aus. Wäre schön, wenn es eine Comparator.min() und Comparator.max() geben würde, um die letzten zwei Zeilen zu vereinfachen. – Mzzzzzz

+2

Gibt es ein Paar in Guava? – ZhongYu

+3

Guave hat kein Paar. –

1

Ein einfacher Ansatz jede wandelbar Pair-Klasse:

final Pair<T, T> pair = new Pair<>(); 
final Comparator<T> comparator = ...; 
Stream.of(...).forEachOrdered(e -> { 
    if(pair.first == null || comparator.compare(e, pair.first) < 0){ 
     pair.first = e; 
    } 
    if(pair.second == null || comparator.compare(e, pair.second) > 0){ 
     pair.second = e; 
    } 
}); 
1

Für eine reine Java-Lösung, die ziemlich präzise ist, können Sie .spähen(). Dies ist nicht wirklich funktional, da alles, was peek() tut, ein Nebeneffekt ist. Aber das macht alles in einem Durchgang, erfordert keine Sortierung und ist nicht zu ausführlich. Es gibt ein "temp" -Objekt, das AtomicRef, aber Sie werden wahrscheinlich ein lokales var/ref zuweisen, um das min und max trotzdem zu halten.

Comparator<T> cmp = ... 
Stream<T> source = ... 
final AtomicReference<T> min = new AtomicReference<T>(); 
Optional<T> max = source.peek(t -> {if (cmp.compare(t,min.get()) < 0) min.set(t);}) 
    .max(cmp); 
//Do whatever with min.get() and max.get() 
+0

Hm ...dies beruht darauf, dass "max" den gesamten "source" -Strom konsumieren muss - ich bin mir nicht sicher, dass dies in irgendeiner Weise garantiert ist (denk nach sortierten Quellen, vielleicht ist ein Kurzschluss möglich?). – Hulk

+0

Das OP sortierte in der ursprünglichen Frage und möchte es vermeiden. Was lässt Sie glauben, dass der Stream nicht garantiert ist? .max (cmp) und .peek() sind beide in der Schnittstelle java.util.stream.Stream definiert und es gibt nichts außerhalb einer Ausnahme, die während der Pipelineverarbeitung ausgelöst wird, die dies verhindern sollte ... – WillD

+0

Ich stimme zu, dass dieser Ansatz mit der aktuelle Version - Ich habe mich nur gefragt, ob es garantiert in zukünftigen Versionen weiter funktionieren würde (siehe zum Beispiel [meine Frage zu 'Stream.count'] (http://Stackoverflow.com/q/41347083/2513200), die das tun werden besuche nicht mehr alle Elemente in Java 9, wenn es die Größe auf eine effizientere Weise bestimmen kann). Aber mit einem benutzerdefinierten 'Comparator' ist eine solche Optimierung hier sowieso nicht möglich. – Hulk

7

Der summarizingInt Sammler funktioniert gut, wenn Sie einen Stream haben von Ganzzahlen.

IntSummaryStatistics stats = Stream.of(2,4,3,2) 
     .collect(Collectors.summarizingInt(Integer::intValue)); 

int min = stats.getMin(); 
int max = stats.getMax(); 

Wenn Sie verdoppelt haben, können Sie den summarizingDouble Sammler verwenden.

DoubleSummaryStatistics stats2 = Stream.of(2.4, 4.3, 3.3, 2.5) 
    .collect(Collectors.summarizingDouble((Double::doubleValue))); 
Verwandte Themen