arg max в потоках Java 8?
Мне часто нужен максимальный элемент коллекции в соответствии с максимизацией критерия, который выдает значение типа double или int. Потоки имеют функцию max(), которая требует от меня реализации компаратора, который я считаю громоздким. Есть ли более лаконичный синтаксис, такой как names.stream().argmax(String::length)
в следующем примере?
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);
}
}
2 ответа
Использование
String longestName = names.stream().max(Comparator.comparing(String::length)).get();
сравнивать элементы по какому-либо свойству (может быть более сложным, но не обязательным).
Как предполагает Брайан в комментариях, используя Optional#get()
как это небезопасно, если есть вероятность того, что Stream
пустой. Вы бы лучше использовать один из более безопасных методов поиска, таких как Optional#orElse(Object)
что даст вам некоторое значение по умолчанию, если нет макс.
Я думаю, что следует учитывать, что в то время как max
/min
уникальны, это, конечно, не гарантируется для argMax
/argMin
; это, в частности, означает, что тип сокращения должен быть коллекцией, такой как, например, List
, Это требует немного больше работы, чем предложено выше.
Следующие ArgMaxCollector<T>
Класс обеспечивает простую реализацию такого сокращения. main
показывает применение такого класса для вычисления argMax
/argMin
из набора строк
one two three four five six seven
упорядочено по длине. Выход (отчет о результате argMax
а также argMin
коллекторы соответственно) должны быть
[three, seven]
[one, two, six]
это две самые длинные и три самые короткие строки соответственно.
Это моя первая попытка использования новых потоковых API Java 8, поэтому любые комментарии будут более чем приветствоваться!
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 ) );
}
}
Простое эффективное решение здесь:
/** return argmin item, else null if none. NAN scores are skipped */
public static <T> T argmin(Stream<T> stream, ToDoubleFunction<T> scorer) {
Double min = null;
T argmin = null;
for (T p: (Iterable<T>) stream::iterator) {
double score = scorer.applyAsDouble(p);
if (min==null || min > score) {
min = score;
argmin = p;
}
}
return argmin;
}