Java 8:从列表中查找最小值的索引

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/31116190/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 18:08:27  来源:igfitidea点击:

Java 8: Find index of minimum value from a List

javajava-8java-stream

提问by Mubin

Say I have a list with elements (34, 11, 98, 56, 43).

假设我有一个包含元素的列表(34, 11, 98, 56, 43)

Using Java 8 streams, how do I find the index of the minimum element of the list (e.g. 1 in this case)?

使用 Java 8 流,如何找到列表中最小元素的索引(例如,在本例中为 1)?

I know this can be done easily in Java using list.indexOf(Collections.min(list)). However, I am looking at a Scala like solution where we can simply say List(34, 11, 98, 56, 43).zipWithIndex.min._2to get the index of minimum value.

我知道这可以在 Java 中使用list.indexOf(Collections.min(list)). 但是,我正在寻找类似 Scala 的解决方案,我们可以简单地说List(34, 11, 98, 56, 43).zipWithIndex.min._2获取最小值的索引。

Is there anything that can be done using streams or lambda expressions (say Java 8 specific features) to achieve the same result.

是否可以使用流或 lambda 表达式(例如 Java 8 特定功能)来实现相同的结果。

Note: This is just for learning purpose. I don't have any problem in using Collectionsutility methods.

注意:这只是为了学习目的。我在使用Collections实用方法时没有任何问题。

回答by Misha

import static java.util.Comparator.comparingInt;

int minIndex = IntStream.range(0,list.size()).boxed()
            .min(comparingInt(list::get))
            .get();  // or throw if empty list

As @TagirValeev mentions in his answer, you can avoid boxing by using IntStream#reduceinstead of Stream#min, but at the cost of obscuring the intent:

正如@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

回答by Alexis C.

You could do it like this:

你可以这样做:

int indexMin = IntStream.range(0, list.size())
                .mapToObj(i -> new SimpleEntry<>(i, list.get(i)))
                .min(comparingInt(SimpleEntry::getValue))
                .map(SimpleEntry::getKey)
                .orElse(-1);

If the list is a random access list, getis a constant time operation. The API lacks of a standard tuple class, so I used the SimpleEntryfrom the AbstractMapclass as a substitute.

如果列表是随机访问列表,get则是一个常数时间操作。该API缺少标准元组类的,所以就用SimpleEntryAbstractMap类作为替代品。

So IntStream.rangegenerates a stream of indexes from the list from which you map each index to its corresponding value. Then you get the minimum element by providing a comparator on the values (the ones in the list). From there you map the Optional<SimpleEntry<Integer, Integer>>to an Optional<Integer>from which you get the index (or -1 if the optional is empty).

因此IntStream.range,从列表中生成索引流,从中您可以将每个索引映射到其相应的值。然后通过在值(列表中的值)上提供比较器来获得最小元素。从那里,您将 映射Optional<SimpleEntry<Integer, Integer>>Optional<Integer>从中获取索引的一个(如果可选为空,则为 -1)。

As an aside, I would probably use a simple for-loop to get the index of the minimum value, as your combination of min/ indexOfdoes 2 passes over the list.

顺便说一句,我可能会使用一个简单的 for 循环来获取最小值的索引,因为min/的组合indexOf确实 2 传递了列表。

You might also be interested to check Zipping streams using JDK8 with lambda (java.util.stream.Streams.zip)

您可能也有兴趣使用带有 lambda 的 JDK8检查压缩流(java.util.stream.Streams.zip)

回答by Cephalopod

Since this is for learning purposes, let's try to find a solution that doesn't just somehow use a stream, but actually works on the stream of our list. We also don't want to assume random access.

由于这是出于学习目的,让我们尝试找到一个不仅以某种方式使用流,而且实际上适用于我们列表的流的解决方案。我们也不想假设随机访问。

So, there are two ways to get a non-trivial result out of a stream: collectand reduce. Hereis a solution that uses a collector:

因此,有两种方法可以从流中获得非平凡的结果:collectreduce是使用收集器的解决方案:

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();
        }
    };

Writing a collectors creates an annoying amount of code, but it can be easily generalized to support any comparable value. Also, calling the collector looks very idiomatic:

编写收集器会产生大量令人讨厌的代码,但它可以很容易地泛化以支持任何可比较的值。此外,调用收集器看起来非常地道:

List<Integer> list = Arrays.asList(4,3,7,1,5,2,9);
int minIndex = list.stream().collect(MIN_INDEX);

If we change the acceptand combinemethods to always return a new Minimuminstance (ie. if we make Minimumimmutable), we can also use reduce:

如果我们将acceptcombine方法更改为始终返回一个新Minimum实例(即,如果我们使之Minimum不可变),我们还可以使用reduce

int minIndex = list.stream().reduce(new Minimum(), Minimum::accept, Minimum::combine).getIndex();

I sense large potential for parallelization in this one.

我感觉到在这方面并行化的巨大潜力。

回答by Tagir Valeev

Here's two possible solutions using my StreamExlibrary:

这是使用我的StreamEx库的两种可能的解决方案:

int idx = IntStreamEx.ofIndices(list).minBy(list::get).getAsInt();

Or:

或者:

int idx = EntryStream.of(list).minBy(Entry::getValue).get().getKey();

The second solution internally is very close to one proposed by @AlexisC. The first one is probably the fastest as it does not use boxing (internallyit's a reduce operation).

内部的第二种解决方案非常接近@AlexisC 提出的解决方案。第一个可能是最快的,因为它不使用装箱(在内部它是一个减少操作)。

Without using third-party code @Misha's answer looks the best for me.

不使用第三方代码@Misha 的答案对我来说是最好的。