[英]Java 8: Find index of minimum value from a List
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 (eg 1 in this case)? 使用Java 8流,如何找到列表中最小元素的索引(例如,在这种情况下为1)?
I know this can be done easily in Java using list.indexOf(Collections.min(list))
. 我知道这可以使用list.indexOf(Collections.min(list))
在Java中轻松完成。 However, I am looking at a Scala like solution where we can simply say List(34, 11, 98, 56, 43).zipWithIndex.min._2
to get the index of minimum value. 但是,我正在寻找类似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 Collections
utility methods. 我在使用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
As @TagirValeev mentions in his answer , you can avoid boxing by using IntStream#reduce
instead of Stream#min
, but at the cost of obscuring the intent: 正如@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
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, get
is a constant time operation. 如果列表是随机访问列表,则get
是恒定时间操作。 The API lacks of a standard tuple class, so I used the SimpleEntry
from the AbstractMap
class as a substitute. API缺少标准的元组类,因此我使用AbstractMap
类中的SimpleEntry
作为替代。
So IntStream.range
generates a stream of indexes from the list from which you map each index to its corresponding value. 因此, IntStream.range
从列表中生成索引流,从中将每个索引映射到其对应的值。 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). 从那里,您将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
/ indexOf
does 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检查Zipping流(java.util.stream.Streams.zip)
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: collect
and reduce
. 因此,有两种方法可以从流中获取非平凡的结果: collect
和reduce
。 Here is a solution that uses a collector: 这是一个使用收集器的解决方案:
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 accept
and combine
methods to always return a new Minimum
instance (ie. if we make Minimum
immutable), we can also use reduce
: 如果我们改变accept
和combine
方法总是返回一个新的Minimum
实例(即如果我们使Minimum
不可变),我们也可以使用reduce
:
int minIndex = list.stream().reduce(new Minimum(), Minimum::accept, Minimum::combine).getIndex();
I sense large potential for parallelization in this one. 我觉得这个并行化的潜力很大。
Here's two possible solutions using my StreamEx library: 以下是使用我的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. 内部的第二个解决方案非常接近@AlexisC提出的解决方案。 The first one is probably the fastest as it does not use boxing ( internally it's a reduce operation). 第一个可能是最快的,因为它不使用装箱( 内部它是一个减少操作)。
Without using third-party code @Misha's answer looks the best for me. 不使用第三方代码@ Misha的回答对我来说是最好的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.