Найти элемент pre-map в потоке, соответствующий минимуму после карты

Я часто делаю что-то вроде этого:

list.stream().min(new Comparator<>() {
 @Override
 public int compare(E a, E b) {
 return ******.compare(f(a),f(b));
 }
})

где f - интенсивная вычислительная функция. Для этого требуется в два раза больше оценок f, которые действительно необходимы. Я бы предпочел

list.stream().mapTo******(f).min()

но тогда я не знаю, как получить исходный элемент, который соответствует этому минимуму.

Один уродливый путь вокруг этого -

class WithF<e>{
 private final E e;
 private final ****** fe;
 WithF(E e, ****** fe){
 this.e = e;
 this.fe = fe;
 }
 public E getE(){
 return e;
 }
 public ****** getFE(){
 return fe;
 }
}
</e>

а затем

list.stream().map(e -> new WithF<>(e,f(e))).min(Comparator.comparing******(WithF::getFE))

Есть ли лучший, идиоматический способ сделать это с помощью API потока?

4 ответа

Это преобразование часто называют Schwartzian Transform

Я бы на самом деле обобщил WithF с несколькими дополнительными битами и переименовал его, чтобы сделать трубку более опрятной.

class SchwartzianKV<e, sortkey="" implements="" comparable<sortkey=""> > 
 implements Comparable<schwartziankv<e, sortkey="">> {
 public final E e;
 public final SORTKEY sortkey;
 SchwartzianKV(E e, SORTKEY sortkey){
 this.e = e;
 this.sortkey = sortkey;
 }
 public static <e, sortkey="" implements="" comparable<sortkey="">> 
 Function<e, schwartziankv<e,="" sortkey="">> transformer( Function<e, sortkey=""> fn ) {
 return new Function<e, schwartziankv<e,sortkey="">>() {
 @Override SchwartzianKV<e,sortkey> apply(E e) {
 return new SchwartzianKV<>(e, fn.apply(e));
 } 
 }
 }
 public int compare(With<e> other) {
 return sortkey.compare(other.sortkey);
 }
}
</e></e,sortkey></e,></e,></e,></e,></schwartziankv<e,></e,>

Теперь вы можете написать поток как

Optional<e> minValue = list.stream()
 .map( SchwartianKV.transformer( e -> f(e) ) )
 .min()
 .map( kv -> kv.e )
</e>

Это довольно краткий.


Как насчет:

Optional<e> minE = list.stream()
 .map(e -> new AbstractMap.SimpleEntry(e, f(e))
 .min(Map.Entry.comparingByValue())
 .map(Map.Entry::getKey);
</e>

Это по существу создает запись карты для каждого из элементов списка для двойного значения, затем находит запись min, используя значения, и, наконец, получает соответствующий ключ.

Обратите внимание, что он возвращает Optional, чтобы разрешить ситуацию, в которой нет элементов в списке, и в этом случае вы получите empty. В качестве альтернативы вы можете добавить вызов orElse в конец, чтобы вернуть еще один E, если список пуст.

Это немного необычное использование Map.Entry(т.е. не класть его на карту). Существуют библиотеки с классом Pair, которые могут выполнять одну и ту же работу, или вы можете создать свой собственный.


В ожидании я опубликую то, что я сейчас рассматриваю:

List<******> fs = list.stream()
 .map(e -> f(e))
 .collect(Collectors.toList())
int i = IntStream.range(0,fs.size()).boxed()
 .min(java.util.Comparator.***************(fs::get))
 .get();
list.get(i)
</******>


list.stream()
 .map(e -> new AbstractMap.SimpleImmutableEntry<>(e,f(e)))
 .min(Map.Entry.comparingByValue())
 .map(Map.Entry::getKey)

в основном использует AbstractMap.SimpleImmutableEntry вместо WithF из вопроса. Не предполагаемое использование Map.Entry так не идеально.

licensed under cc by-sa 3.0 with attribution.