2017-01-23 27 views
12

是否有簡明的方法來提取一個流中的最小值和最大值(基於某個比較器)在一個通道?簡明的方式來獲得Java 8流的最小值和最大值

似乎有很多方法可以得到的最大值和最小值分別,或者我可以流分類到一個臨時對象,例如:

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

但這不是簡潔,需要分配臨時對象。我寧願不分配臨時對象或在流中進行兩次傳遞。有其他選擇嗎?

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

你有沒有考慮像[IntSummaryStatistics]收集器(https://docs.oracle.com/javase/8/docs/api/java/util/IntSummaryStatistics.html)?你可以按照模式假設這不是數字。 –

回答

13

如果這是一個經常需要的功能,我們最好做一個Collector來完成這項工作。我們需要一個Stats類來保存count, min, max,以及工廠方法來創建統計收集器。

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

fooStream.collect(Stats.collector(fooComparator)) 

(也許有更好的簡便方法是Stats.collect(stream)

我做出了榜樣Stats類 -

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

我不會指定'UNORDERED',因爲這個收集器能夠尊重遇到順序,也就是說,如果有一個領帶,就像'max(...)'和'min(...)一樣提供最大/最小元素的第一個。 '做。 – Holger

+0

'IntSummaryStatistics'更好 –

4

將流的每個元素映射到一對,其中兩個元素表示min和max;然後通過獲取分鐘的最小值和最大值的最大值來減少對。

例如,使用一些Pair類和一些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

不像我期待的那麼簡潔,但是這看起來不錯。如果有一個Comparator.min()和Comparator.max()來簡化最後兩行,會很好。 – Mzzzzzz

+2

番石榴是否有一對? – ZhongYu

+3

番石榴沒有一對。 –

1

使用任何可變Pair類的直接方法:

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

對於一個純Java的解決方案,是相當簡潔,你可以使用。窺視()。這不是真正的功能,因爲.peek()所做的任何事情都是一種副作用。但是這確實能夠一次完成,不需要排序,也不會太冗長。有一個「臨時」對象AtomicRef,但是你可能會分配一個本地var/ref來保存最小和最大值。

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

嗯......這依賴於'max'不得不消耗整個'源'流 - 我不確定這是否以任何方式得到保證(思考排序的來源,短路也許可能?)。 – Hulk

+0

OP在原始問題中排序並希望避免它。是什麼讓你相信消費流不能得到保證? .max(cmp)和.peek()都在java.util.stream.Stream接口上定義,並且在流水線處理期間拋出的Exception之外沒有任何東西可以阻止這個... – WillD

+0

我同意這種方法適用於目前的版本 - 我只是想知道它是否能夠在未來的版本中繼續工作(參見例如[我的關於Stream.count的問題](http://stackoverflow.com/q/41347083/2513200)如果能夠以更高效的方式確定大小,則不再訪問java 9中的所有元素)。但是用一個自定義的比較器,這樣的優化在這裏可能是不可能的。 – Hulk

7

summarizingInt集熱效果很好,如果你有一個流整數。

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

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

如果你有雙打你可以使用summarizingDouble收藏家。

DoubleSummaryStatistics stats2 = Stream.of(2.4, 4.3, 3.3, 2.5) 
    .collect(Collectors.summarizingDouble((Double::doubleValue))); 
相關問題