2014-12-22 17 views
5

Spesso ho bisogno dell'elemento massimo di una collezione in base alla massimizzazione di un criterio che produce un valore doppio o int. Gli stream hanno la funzione max() che mi impone di implementare un comparatore, che trovo ingombrante. Esiste una sintassi più concisa, ad esempio names.stream().argmax(String::length) nell'esempio seguente?arg max in Java 8 flussi?

import java.util.Arrays; 
import java.util.Comparator; 
import java.util.List; 

public class ArgMax 
{ 
    public static void main(String[] args) 
    { 
     List<String> names = Arrays.asList("John","Joe","Marilyn"); 
     String longestName = names.stream().max((String s,String t)->(Integer.compare(s.length(),t.length()))).get(); 
     System.out.println(longestName); 
    } 
} 

risposta

10

Usa

String longestName = names.stream().max(Comparator.comparing(String::length)).get(); 

per confrontare gli elementi su alcune proprietà (può essere più complesso di quello, ma non deve).

As Brian suggests in the comments, utilizzando Optional#get() come questo non è sicuro se c'è una possibilità che il Stream è vuoto. Saresti più adatto a utilizzare uno dei metodi di recupero più sicuri, come ad esempio Optional#orElse(Object) che ti darà un valore predefinito se non c'è un limite massimo.

+6

Gah! Non utilizzare raw 'get()' alla fine, altrimenti lanci NSEE se lo stream è vuoto. Utilizza un metodo sicuro come 'orElse',' orElseThrow' o 'ifPresent'. –

+5

@BrianGoetz Penso che abbiamo scoperto una delle allergie di Brian. Starnutisce ogni volta che scrivo anche 'Optional.get()'. –

+2

@StuartMarks Effettivamente così. Non avremmo mai dovuto chiamarlo 'get'. Avremmo dovuto chiamarlo 'getOrThrow()' o 'orElseThrow()' o 'getUnsafely()' o probabilmente solo 'getOrThrowNoSuchElementExcpetionIfThisOptionalIsNotPresent()'. –

3

Penso che mentre il max/min sia unico, questo non è ovviamente garantito per lo argMax/argMin; ciò in particolare implica che il tipo di riduzione debba essere una raccolta, come ad esempio uno List. Ciò richiede un po 'più di lavoro di quanto suggerito sopra.

La seguente classe ArgMaxCollector<T> fornisce una semplice implementazione di tale riduzione. Il main mostra un'applicazione di tale classe per calcolare la argMax/argMin del set di stringhe

one two three four five six seven 

ordinati per la loro lunghezza. L'uscita (comunicazione dei risultati delle argMax e argMin collettori rispettivamente) dovrebbe essere

[three, seven] 
[one, two, six] 

che sono le due e le tre corde breve Più lungo rispettivamente.

Questo è il mio primo tentativo di utilizzare le nuove API di flusso Java 8, quindi qualsiasi commento sarà più che benvenuto!

import java.util.ArrayList; 
import java.util.Arrays; 
import java.util.Comparator; 
import java.util.List; 
import java.util.stream.Collector; 

class ArgMaxCollector<T> { 

    private T max = null; 
    private ArrayList<T> argMax = new ArrayList<T>(); 
    private Comparator<? super T> comparator; 

    private ArgMaxCollector(Comparator<? super T> comparator) { 
     this.comparator = comparator; 
    } 

    public void accept(T element) { 
     int cmp = max == null ? -1 : comparator.compare(max, element); 
     if (cmp < 0) { 
      max = element; 
      argMax.clear(); 
      argMax.add(element); 
     } else if (cmp == 0) 
      argMax.add(element); 
    } 

    public void combine(ArgMaxCollector<T> other) { 
     int cmp = comparator.compare(max, other.max); 
     if (cmp < 0) { 
      max = other.max; 
      argMax = other.argMax; 
     } else if (cmp == 0) { 
      argMax.addAll(other.argMax); 
     } 
    } 

    public List<T> get() { 
     return argMax; 
    } 

    public static <T> Collector<T, ArgMaxCollector<T>, List<T>> collector(Comparator<? super T> comparator) { 
     return Collector.of(
      () -> new ArgMaxCollector<T>(comparator), 
      (a, b) -> a.accept(b), 
      (a, b) ->{ a.combine(b); return a; }, 
      a -> a.get() 
     ); 
    } 
} 

public class ArgMax { 

    public static void main(String[] args) { 

     List<String> names = Arrays.asList(new String[] { "one", "two", "three", "four", "five", "six", "seven" }); 

     Collector<String, ArgMaxCollector<String>, List<String>> argMax = ArgMaxCollector.collector(Comparator.comparing(String::length)); 
     Collector<String, ArgMaxCollector<String>, List<String>> argMin = ArgMaxCollector.collector(Comparator.comparing(String::length).reversed()); 

     System.out.println(names.stream().collect(argMax)); 
     System.out.println(names.stream().collect(argMin)); 

    } 

}