2017-01-23 8 views
12

Bir geçişte (bazı karşılaştırıcılara dayalı olarak) hem minimum hem de maksimum değerini ayıklamak için kısa bir yol var mı?Java 8 akışının minimum ve maksimum değerini elde etmenin özlü yolu

bireysel min almak için birçok yol ve maksimum değerleri Orada görünüyor, yoksa örneğin bir geçici nesnesine akışı sıralayabilirsiniz:

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

Ama bu özlü değildir ve tahsis gerektirir geçici nesne Geçici bir nesne ayırmamayı veya akıştan iki geçiş yapmayı tercih ederim. Alternatif var mı?

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

sen [IntSummaryStatistics] gibi bir kollektörü düşündünüz mü (https://docs.oracle.com/javase/8/docs/api/java/util/IntSummaryStatistics.html)? Kalıbın izini takip edebilirsiniz, bu sayılarla ilgili değildir. –

cevap

13

, daha iyi iş yapmak için bir Collector olun. count, min, max'u tutturmak için Stats sınıfına ve istatistik yöntemleri oluşturmak için fabrika yöntemlerine ihtiyacımız var. Eğer bir Akış varsa

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" özelliğini belirtmem, çünkü bu toplayıcı karşılaşma sırasına uymayı becerebiliyor, yani maksimum/minimal öğelerin ilkini sağla, tam olarak "max (…)" ve "min (…)" gibi yap. – Holger

+0

'IntSummaryStatistics' daha iyidir –

4

Akışın her bir öğesinin, iki öğenin min ve maks; ve daha sonra çiftleri minimuma indirerek çiftleri küçültün.

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

Umduğum kadar kısa değil ama bu iyi görünüyor. Son iki satırı basitleştirmek için bir Comparator.min() ve Comparator.max() olsaydı hoş olurdu. – Mzzzzzz

+2

Guava'da bir çift var mı? – ZhongYu

+3

Guava adlı kullanıcının çifti yok. –

1

herhangi değişken Pair sınıfını kullanan bir açık yaklaşım: oldukça özlü olan bir saf Java çözüm için

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

, kullanabileceğiniz bazı Pair sınıf ve bazı Comparator<T> kullanarak Örneğin

, .dikizlemek(). Bu gerçekten işlevsel değildir, çünkü .peek() 'in bir yan etkisi olduğu gibi. Ama bu her şeyi bir geçişte yapıyor, sıralama gerektirmiyor ve çok ayrıntılı değil. Bir "temp" Nesne, AtomicRef var, ama muhtemelen her zaman min ve max tutmak için bir yerel var/ref tahsis edeceksin. Bu sık ihtiyaç duyulan bir özellik ise

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 ...Bu, "kaynak" akışının tamamını tüketmek zorunda olan "max" a dayanır - herhangi bir şekilde garanti edildiğinden emin değilim (sıralanmış kaynaklar düşünmek, kısa devre belki de mümkün olabilir mi?). – Hulk

+0

OP orijinal soruda sıralama yapıyordu ve bundan kaçınmak istiyordu. Akışı tüketmenin garanti edilmediğine inanmanızı sağlayan şey nedir? .max (cmp) ve .peek() her ikisi de java.util.stream.Stream arabiriminde tanımlanır ve boru hattının işlenmesi sırasında atılan bir Özel Durumun dışında hiçbir şey yoktur, bu da bunu engellemelidir ... – WillD

+0

Bu yaklaşımın Şu anki sürüm - Gelecek sürümlerde çalışmaya devam edeceğinin garanti edilip edilmediğini merak ettim (örneğin bakınız [Stream.count] ile ilgili sorum [http://stackoverflow.com/q/41347083/2513200] boyutu daha verimli bir şekilde belirleyebiliyorsa, java 9'daki tüm öğeleri artık ziyaret etmeyin). Ancak, bir "Karşılaştırıcı" özelliğiyle, böyle bir optimizasyon muhtemelen burada mümkün değildir. – Hulk

7

summarizingInt kollektör iyi çalışıyor -

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

fooStream.collect(Stats.collector(fooComparator)) 

Ben Bir örnek Stats sınıf yapılan

(Belki daha iyi bir rahatlık yöntemi Stats.collect(stream) olurdu) Tamsayılar. Eğer çiftler varsa

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

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

Eğer summarizingDouble kollektörü kullanabilirsiniz.

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