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 ответ выглядит лучше для меня.