繁体   English   中英

Java 8:从List中查找最小值索引

[英]Java 8: Find index of minimum value from a List

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

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

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

有没有什么可以使用流或lambda表达式(比如Java 8特定功能)来实现相同的结果。

注意:这仅用于学习目的。 我在使用Collections实用程序方法时没有任何问题。

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 IntStream#reduce来避免装箱,但是以掩盖意图为代价:

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缺少标准的元组类,因此我使用AbstractMap类中的SimpleEntry作为替代。

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

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

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

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

因此,有两种方法可以从流中获取非平凡的结果: 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();
        }
    };

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

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

如果我们改变acceptcombine方法总是返回一个新的Minimum实例(即如果我们使Minimum不可变),我们也可以使用reduce

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

我觉得这个并行化的潜力很大。

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

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

要么:

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM