fujitsu fiji
fujitsu fiji

Reputation: 97

How to get the index of Arrays.stream(array).min()?

I want to get the index of min, I tried ways like getIndexOf etc. but none of them worked. How can I do this?

import java.util.Arrays;
class getIndexOfMin {
    public static void main(String[] args) {

        double arr[] = {263.5, 393.75, 5.0, 289.75};

        double min = Arrays.stream(arr).min().getAsDouble();
        
        System.out.println(min);
    }
}

Upvotes: 5

Views: 711

Answers (5)

Panagiotis Bougioukos
Panagiotis Bougioukos

Reputation: 19108

I think the following solution should be preferred, as in 1 iteration of the stream you collect both the required index and the required value.

double arr[] = {263.5, 393.75, 5.0, 289.75};

Optional<Map.Entry<Integer, Double>> keyValueOpt = IntStream.range(0, arr.length)
              .mapToObj(i -> Map.entry(i,arr[i]))
              .min(Map.Entry.comparingByValue());

keyValueOpt.ifPresent(keyValue -> System.out.println(String.format("Index %s with min value %s", keyValue.getKey(), keyValue.getValue())));

enter image description here

You also don't need any new classes to be introduced to your code as the Map.Entry(K k, V v) fits the role of keeping a single pair of Key, Value to your result.

Upvotes: 0

Alexander Ivanchenko
Alexander Ivanchenko

Reputation: 29028

Here's how it can be done without iterating over the array indices and trying to mimic for-loop with a stream.

And we can also grab the actual minimal value along the way.

For that we can use DoubleStream.collect() which expects three arguments:

  • Supplier<R> supplier - provides a mutable object which would serve as a container of data;
  • ObjDoubleConsumer<R> accumulator - determines how stream elements should be accumulated in the mutable collector provided by the supplier;
  • BiConsumer<R,R> combiner - combines partial results while executing the stream in parallel.

As a mutable container which should be provided by the supplier we can use an array double[] (if you have a look at the source code of Collectors that are intended to accumulate primitive values, namely summingInt(), summingDouble(), etc. you would probably find some similarities).

double[] arr = {263.5, 393.75, 5.0, 289.75};
        
double[] min = Arrays.stream(arr)
    .collect(
        () -> new double[]{0, -1, 0},    // supplier
        (double[] res, double next) -> { // accumulator
            res[2]++; // element count
            if (res[1] == -1 || res[0] > next) {
                res[0] = next;          // min value
                res[1] = res[2] - 1;    // corresponding index (element count - 1)
            }
        },
        (left, right) -> {              // combiner
            if (left[0] > right[0]) {
                left[0] = right[0];
                left[1] = left[2] + right[1];
            }
            left[2] += right[2];
        }
    );
        
System.out.printf("Min value is %s at index: %d", min[0], (int) min[1]);

Output:

Min value is 5.0 at index: 2

The logic for determining the min value and tracking the number of consumed elements shown above can be encapsulated into a class (as suggested by @Holger) which would be used as the accumulation type instread of array.

For convenience, I've implemented DoubleConsumer interface, its method accept() would be used to implement accumulator. And method merge() would be used in the combiner.

public static class MinValueAndIndex implements DoubleConsumer {
    private int totalCount;
    private int index;
    private double min;

    @Override
    public void accept(double value) {
        if (totalCount == 0 || value < min) {
            min = value;
            index = totalCount;
        }
        totalCount++;
    }
    
    public void merge(MinValueAndIndex other) {
        if (min > other.min) {
            min = other.min;
            index = totalCount + other.index;
        }
        totalCount += other.totalCount;
    }
    
    // getters
}

The stream would look like that:

double[] arr = {263.5, 393.75, 5.0, 289.75};
        
MinValueAndIndex valueIndex = Arrays.stream(arr)
    .collect(
        MinValueAndIndex::new,
        MinValueAndIndex::accept,
        MinValueAndIndex::merge
    );
    
System.out.printf("Min value is %s at index: %d", valueIndex.getMin(), valueIndex.getIndex());

Output:

Min value is 5.0 at index: 2

Upvotes: 1

Chaosfire
Chaosfire

Reputation: 6985

You can use a custom class to hold together an index and the value at this index. The task to find the index with min value becomes trivial then, and you can find the index and value all at once.

A record is perfect for this purpose, if you use version before java 14, you can just use normal class.

public record IndexValue(int index, double value) {
}

Example usage:

public class Test {

  public static void main(String[] args) {
    double[] arr = {263.5, 393.75, 5.0, 289.75};
    IndexValue min = IntStream.range(0, arr.length) //stream indexes
            .mapToObj(i -> new IndexValue(i, arr[i])) //map index and value together using the class
            .min(Comparator.comparing(IndexValue::value)) //find minimum by comparing values
            .orElseGet(() -> new IndexValue(-1, Double.MAX_VALUE)); //supply default value suiting your needs, if array was empty
    System.out.println("Min index value - " + min);
  }
}

Prints - Min index value - IndexValue[index=2, value=5.0]. All operations in this stream are non-interfering and stateless, and it's safe for parallel execution.

Upvotes: 0

Sweeper
Sweeper

Reputation: 272685

You can make a stream of the indices of the array instead, from 0 to arr.length (exclusive):

Optional<Integer> minIndex = IntStream.range(0, arr.length).boxed()
    .min(Comparator.comparingDouble(i -> arr[i]));

Note that this is an optional, as the array could be empty. Also, when there are multiple minimums, it is not specified which one's index it will return. In the implementation that I'm using (OpenJDK 17), it returns the first one's index.

If you want to control which minimum's index you get, you can do:

Optional<Integer> minIndex = IntStream.range(0, arr.length).boxed().reduce(
    // gets the first one's:
    (a, b) -> arr[a] <= arr[b] ? a : b
    // gets the last one's:
    // (a, b) -> arr[a] < arr[b] ? a : b
);

Upvotes: 4

Cole Henrich
Cole Henrich

Reputation: 165

int index;
for (int i = 0; i < arr.length; i++){
    double d = arr[i];
    if (d == min){
       index = i; 
       break;
    }
}

Upvotes: 2

Related Questions