Java 8: найти индекс минимального значения из списка
скажем у меня есть список с элементами (34, 11, 98, 56, 43)
.
используя потоки Java 8, Как найти индекс минимального элемента списка (например, 1 в этом случае)?
Я знаю, что это можно легко сделать в Java, используя list.indexOf(Collections.min(list))
. Тем не менее, я смотрю на решение Scala, где мы можем просто сказать List(34, 11, 98, 56, 43).zipWithIndex.min._2
чтобы получить индекс минимального значения.
есть ли что-нибудь, что можно сделать с помощью потоков или лямбда-выражений (скажем, Java 8 specific features) для достижения результат тот же.
Примечание: это только для учебных целей. У меня нет проблем с использованием Collections
служебные методы.
4 ответов
import static java.util.Comparator.comparingInt;
int minIndex = IntStream.range(0,list.size()).boxed()
.min(comparingInt(list::get))
.get(); // or throw if empty list
как @ TagirValeev упоминает в ответ, вы можете избежать бокса с помощью IntStream#reduce
вместо Stream#min
, но ценой затемнения намерения:
int minIdx = IntStream.range(0,list.size())
.reduce((i,j) -> list.get(i) > list.get(j) ? j : i)
.getAsInt(); // or throw
вы могли бы сделать это вот так:
int indexMin = IntStream.range(0, list.size())
.mapToObj(i -> new SimpleEntry<>(i, list.get(i)))
.min(comparingInt(SimpleEntry::getValue))
.map(SimpleEntry::getKey)
.orElse(-1);
если список является списком произвольного доступа,get
- операция постоянного времени. API не хватает стандартного класса кортежа, поэтому я использовал SimpleEntry
С AbstractMap
класса в качестве замены.
так IntStream.range
генерирует поток индексов из списка, из которого вы сопоставляете каждый индекс с его соответствующим значением. Затем вы получаете минимальный элемент, предоставляя компаратор по значениям (тем, что есть в списке). Оттуда вы карту Optional<SimpleEntry<Integer, Integer>>
до Optional<Integer>
из которого вы получаете индекс (или -1, если опция пуста).
в стороне, я бы, вероятно, использовал простой for-loop, чтобы получить индекс минимального значения, как вашу комбинацию min
/ indexOf
делает 2 прохода по списку.
Вам также может быть интересно проверить сжатие потоков с помощью JDK8 с лямбда (java.утиль.поток.Потоки.zip)
поскольку это для учебных целей, давайте попробуем найти решение, которое не просто каким-то образом использует поток, но на самом деле работает в потоке нашего списка. Мы также не хотим предполагать случайный доступ.
Итак, есть два способа получить нетривиальный результат из потока: collect
и reduce
. здесь - это решение, которое использует взыскателя:
class Minimum {
int index = -1;
int range = 0;
int value;
public void accept(int value) {
if (range == 0 || value < this.value) {
index = range;
this.value = value;
}
range++;
}
public Minimum combine(Minimum other) {
if (value > other.value) {
index = range + other.index;
value = other.value;
}
range += other.range;
return this;
}
public int getIndex() {
return index;
}
}
static Collector<Integer, Minimum, Integer> MIN_INDEX = new Collector<Integer, Minimum, Integer>() {
@Override
public Supplier<Minimum> supplier() {
return Minimum::new;
}
@Override
public BiConsumer<Minimum, Integer> accumulator() {
return Minimum::accept;
}
@Override
public BinaryOperator<Minimum> combiner() {
return Minimum::combine;
}
@Override
public Function<Minimum, Integer> finisher() {
return Minimum::getIndex;
}
@Override
public Set<Collector.Characteristics> characteristics() {
return Collections.emptySet();
}
};
написание коллекторов создает раздражающее количество кода, но его можно легко обобщить на поддержите любое сопоставимое значение. Кроме того, вызов коллектора выглядит очень идиоматично:
List<Integer> list = Arrays.asList(4,3,7,1,5,2,9);
int minIndex = list.stream().collect(MIN_INDEX);
если мы изменим accept
и combine
методы, чтобы всегда возвращать новый Minimum
экземпляр (т. е. если мы сделаем Minimum
неизменяемым), мы также можем использовать reduce
:
int minIndex = list.stream().reduce(new Minimum(), Minimum::accept, Minimum::combine).getIndex();
Я чувствую большой потенциал для распараллеливания в этом.
вот два возможных решения с использованием my StreamEx библиотека:
int idx = IntStreamEx.ofIndices(list).minBy(list::get).getAsInt();
или:
int idx = EntryStream.of(list).minBy(Entry::getValue).get().getKey();
второе решение внутренне очень близко к тому, которое было предложено @AlexisC. Первый, вероятно, самый быстрый, поскольку он не использует бокс (внутри это операция сокращения).
без использования стороннего кода @ misha's ответ выглядит лучше для меня.