Java 8: Find index of minimum value from a List - java

Say I have a list with elements (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)?
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._2 to get the index of minimum value.
Is there anything that can be done using streams or lambda expressions (say Java 8 specific features) to achieve the same result.
Note: This is just for learning purpose. I don't have any problem in using Collections utility methods.

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:
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. The API lacks of a standard tuple class, so I used the SimpleEntry from the AbstractMap class as a substitute.
So IntStream.range generates 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).
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.
You might also be interested to check Zipping streams using JDK8 with lambda (java.util.stream.Streams.zip)

Here's two possible solutions using my StreamEx library:
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 (internally it's a reduce operation).
Without using third-party code #Misha's answer looks the best for me.

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. 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:
int minIndex = list.stream().reduce(new Minimum(), Minimum::accept, Minimum::combine).getIndex();
I sense large potential for parallelization in this one.

Related

Java Stream minimum with known bound [duplicate]

This question already has answers here:
How to short-circuit a reduce() operation on a Stream?
(4 answers)
Closed 3 years ago.
I have a Stream of Lists of which I want to get the entry with the least elements. I could of course do something like
Stream<List<T>> s = ...
s.min((e1, e2) -> e1.size() - e2.size());
But in a case like this, we know a lower bound for the minimum, since the size is non-negative. Meaning the moment a List of size 0 is found, we could actually stop, instead of running through the rest of the list too.
Can this be achieved in a decent way with Java Streams?
I would imagine it looking something like this, giving a comparator and a function which tells us when the current minimum is a global one:
s.boundedMin(
(e1, e2) -> e1.size() - e2.size(),
e -> e.size() == 0
)
I can't think of a way to implement this.
Of course I could just use an Iterable and use a loop with break-statement to get this, I just wondered if streams could get me there too.
Edit:
To make it a bit clearer. The Stream might or might not contain Lists of size 0. My issue is that min() will run through the whole stream, even if it already found a list of size 0 (which is already as small as it can ever get). So, what I'm looking for is an implementation of min which does not need to scan through the whole stream, by providing a lower bound for the minimum.
Edit2:
An equivalent iterative solution without streams would be
List<List<T>> s = ...
List<T> min = null;
for (List<T> l : s) {
if (min == null || min.size() > l.size())
min = l;
if (min.size() == 0) {
break;
}
}
Just for the fun of it:
static <T> int size(Stream<List<T>> st) {
class MinHolder implements Consumer<List<T>> {
private int min = Integer.MAX_VALUE;
public void accept(List<T> l) {
if (min > l.size()) {
min = l.size();
}
}
}
MinHolder holder = new MinHolder();
Spliterator<List<T>> sp = st.spliterator();
int elements = 0;
for (; sp.tryAdvance(holder) && holder.min > 0; ++elements) {
}
System.out.printf("took %s elements to find the min%n", elements);
return holder.min;
}
And a few test cases:
public static void main(String[] args) {
Stream<List<Integer>> st = Stream.of(List.of());
System.out.println(size(st));
st = Stream.empty();
System.out.println(size(st));
st = Stream.of(List.of(), List.of(1), List.of(1, 2), List.of(1, 2, 3));
System.out.println(size(st));
}
If you are not forced to use a Stream<List<T>> then don't; this conditional breaking is not something Streams were designed for and would be considered an abuse by many.

Partially sort an array in descending order using Java Stream API

I need to know how to partially sort an array of primitive unique integers in descending order using Stream API. For example, if there is an array like {1,2,3,4,5}, I want to get {5,4,3, 1,2} - 3 biggest elements first and then the rest. Is it even possible using streams? I checked the docs - there are two methods skip and limit but they change the stream content and work from the beginning of the array.
I can sort the whole array like
Arrays.stream(arr)
.boxed()
.sorted(Collections.reverseOrder())
.mapToInt(Integer::intValue)
.toArray();
but how to make this sorting partial? I said Stream API because I want it to be written nicely.
Also I intuitively feel that concat may have a go here. Another approach I could think about - is to use a custom comparator limiting the number of sorted elements. What do you think?
P.S. I am not a Java expert.
Though the code is longer than the accepted answer, it does a lot less sorting: for big arrays this will matter:
private static int[] partiallySorted(int[] input, int bound) {
int[] result = new int[input.length];
int i = -1;
PriorityQueue<Integer> pq = new PriorityQueue<>(bound, Comparator.naturalOrder());
for (int x : input) {
pq.add(x);
if (pq.size() > bound) {
int el = pq.poll();
result[bound + ++i] = el;
}
}
while (!pq.isEmpty()) {
result[--bound] = pq.poll();
}
return result;
}
Here's an approach using streams.
int[] sortPartially(int[] inputArray, int limit) {
Map<Integer, Long> maxValues = IntStream.of(inputArray)
.boxed()
.sorted(Comparator.reverseOrder())
.limit(limit)
.collect(Collectors.groupingBy(x -> x, LinkedHashMap::new, Collectors.counting()));
IntStream head = maxValues.entrySet()
.stream()
.flatMapToInt(e -> IntStream.iterate(e.getKey(), i -> i)
.limit(e.getValue().intValue()));
IntStream tail = IntStream.of(inputArray)
.filter(x -> {
Long remainingDuplication = maxValues.computeIfPresent(x, (y, count) -> count - 1);
return remainingDuplication == null || remainingDuplication < 0;
});
return IntStream.concat(head, tail).toArray();
}
Above example of course sorts the entire input array, but keeps the order of unsorted elements stable.
Another stream example using priority queue (as others mentioned) reduces the runtime complexity:
Collection<Integer> sortPartially(int[] inputArray, int sortedPartLength) {
Queue<Integer> pq = new PriorityQueue<>(sortedPartLength);
Deque<Integer> result = IntStream.of(inputArray).boxed().map(x -> {
pq.add(x);
return pq.size() > sortedPartLength ? pq.poll() : null;
}).filter(Objects::nonNull).collect(Collectors.toCollection(ArrayDeque::new));
Stream.generate(pq::remove).limit(sortedPartLength).forEach(result::addFirst);
return result;
}
If there are duplicates in the input array, the order of unsorted elements can change.
I need to know how to partially sort an array of primitive integers in descending order using Stream API.
There is no built-in tool that lets you do this in Java. Neither in the Stream API nor in the Collections API. You either need to implement it on your own or change your approach.
I said Stream API because I want it to be written nicely.
Using Java 8 Streams does not mean that your code will be written nicely. Streams are not universal tools. Sometimes they offer enhanced readability and sometimes you have to use something else.
Another approach I could think about - is to use a custom comparator limiting the number of sorted elements.
That can't be done, since Comparator does not know how many elements have been sorted. Simply counting the calls will not give you any meaningful information in this regard.
What I would suggest is implementing something like C++'s std::partial_sort, which is most likely based on the heap approach.
I would save the three largest elements in a set and then define my own comparator.
public static void main(String[] args){
int[] input = {1,2,3,4,5};
Set<Integer> set = Arrays.stream(input).boxed().sorted(Comparator.reverseOrder()).limit(3).collect(Collectors.toSet());
Comparator<Integer> customComp = (a,b) -> {
if(set.contains(a) && set.contains(b)){ return a.compareTo(b);}
else if(set.contains(a)){ return 1;}
else if(set.contains(b)){ return -1;}
else { return 0;}
};
int[] sorted = Arrays.stream(input).boxed().sorted(customComp.reversed()).mapToInt(i->i).toArray();
System.out.println(Arrays.toString(sorted));
}
You won't be able to do this very nicely using streams. Here is one way to do it:
public static void main(String[] args) {
Integer[] arr = {1, 2, 3, 4, 5};
List<Integer> originalValues = new ArrayList<>(Arrays.asList(arr));
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < 3; i++) {
originalValues.stream().max(Integer::compareTo).ifPresent(v -> {
list.add(v);
originalValues.remove(v);
});
}
list.addAll(originalValues);
System.out.println(list);
// [5, 4, 3, 1, 2]
}

get the index of the min size list in an arrayList using java stream

I have a list of Integer List, like list1=(1,2,3) and list2 = (0,1).
my list of lists contains list1 and list2. It could contains more but for the example i took only two lists.
The question is to get the index of the list with minimum size using java stream.
Here is my program and it work using only the for loop method.
import java.util.ArrayList;
public class Example {
public static void main( String[] args ) {
ArrayList<Integer> list1 = new ArrayList<>();
list1.add(1);list1.add(2);list1.add(3);
ArrayList<Integer> list2 = new ArrayList<>();
list2.add(0);list2.add(1);
ArrayList<ArrayList<Integer>> listOLists = new ArrayList<>();
listOLists.add(list1);
listOLists.add(list2);
printTheIndexOfTheListWithTheMinSize(listOLists);
}
private static void printTheIndexOfTheListWithTheMinSize( ArrayList<ArrayList<Integer>> listOLists ) {
int minSize = listOLists.get(0).size();
int minIndex = 0;
int i=0;
for ( ArrayList<Integer> list: listOLists ) {
if (list.size()<minSize)
{
minSize = list.size();
minIndex=i;
}
i++;
}
System.out.println(minIndex);
}
}
Could you please give me a hint how to do that using Java stream API.
Note that i'm calling this method many times in a heavy calcul, so the awnser should take that in consideration.
Not really elegant, because it requires boxing and unboxing, but...
Optional<Integer> minIndex =
IntStream.range(0, list.size())
.boxed()
.min(Comparator.comparingInt(i -> list.get(i).size()));
One way to possibly do that would be using indexOf and Collections.min with a comparator as:
int minIndex = listOLists.indexOf(Collections.min(listOLists,
Comparator.comparingInt(List::size)));
Stay away from solutions using indexOf. While they may allow to write rather short code, this indexOf operation bears a content-based linear search operation, invoking equals on the list elements until a match is found.
While it might look like a trivial thing, as all sub-lists differ in size, except for the matching element, most of Java 8’s List implementations do not use the size to short-cut the comparison.
To illustrate the issue,
use the following helper class
class Counter {
int count;
#Override
public boolean equals(Object obj) {
count++;
return super.equals(obj);
}
#Override
public int hashCode() {
return super.hashCode();
}
#Override
public String toString() {
return "equals invoked "+count+" times";
}
}
and
Counter c = new Counter();
List<List<Counter>> list = Arrays.asList(
new ArrayList<>(Collections.nCopies(10, c)),
new ArrayList<>(Collections.nCopies(15, c)),
new ArrayList<>(Collections.nCopies(7, c)),
new ArrayList<>(Collections.nCopies(10, c))
);
Comparator<List<?>> cmp = Comparator.comparingInt(List::size);
System.out.println("using IntStream.range(0, list.size()).boxed()\r\n" +
" .min(Comparator.comparing(list::get, cmp))");
int minIndex =
IntStream.range(0, list.size()).boxed()
.min(Comparator.comparing(list::get, cmp)).orElse(-1);
System.out.println("result "+minIndex+", "+c);
c.count = 0;
System.out.println("\nusing list.indexOf(Collections.min(list, cmp))");
minIndex = list.indexOf(Collections.min(list, cmp));
System.out.println("result "+minIndex+", "+c);
c.count = 0;
System.out.println("\nusing list.indexOf(list.stream().min(cmp).get())");
minIndex = list.indexOf(list.stream().min(cmp).get());
System.out.println("result "+minIndex+", "+c);
it will print
using IntStream.range(0, list.size()).boxed()
.min(Comparator.comparing(list::get, cmp))
result 2, equals invoked 0 times
using list.indexOf(Collections.min(list, cmp))
result 2, equals invoked 14 times
using list.indexOf(list.stream().min(cmp).get())
result 2, equals invoked 14 times
in Java 8, showing that calling equals on any contained element is an unnecessary operation (see the first variant, derived from this answer), but performed multiple times for the other variants. Now imagine what happens if we use larger lists and/or a larger number of lists and have an element type with a rather expensive equality test.
Note that for ArrayList, this has been solved in JDK 11, but there are still list implementations left, like the ones returned by Collections.nCopies or Arrays.asList, which do not short circuit, so it’s generally preferable not to do an entirely obsolete content based linear search operation.
Here's one way to go about it:
int index = listOLists.indexOf(listOLists.stream()
.min(Comparator.comparingInt(List::size))
.orElseGet(ArrayList::new));
or if you want to avoid the creation of an ArrayList when the source is empty then you could do:
int index = listOLists.isEmpty() ? -1 : listOLists.indexOf(listOLists.stream()
.min(Comparator.comparingInt(List::size)).get());
An alternative that creates index/size arrays and finds the min by size:
IntStream.range(0, listOLists.size())
.mapToObj(i -> new int[] { i, listOLists.get(i).size() })
.min(Comparator.comparingInt(arr -> arr[1]))
.map(arr -> arr[0])
.ifPresent(System.out::println);
This will print the index of min-sized list in listOLists

Java program to count positive values in a Collection<Integer>

I am working on a program to count the positive values in an integer Collection and having an issue. I am somewhat new to Java, and wondering if someone would be able to point out where I went wrong.
public class CountPositives {
/**
* Returns the number of positive values in the given Collection.
*/
public static int countPositives(Collection<Integer> collection) {
List<Integer> copy = new ArrayList(collection);
int positive = 0;
Iterator<Integer> itr = copy.iterator();
for (int i = 0; i < copy.size(); i++) {
if (copy.get(i) > 0 ) {
positive ++;
}
}
return positive;
}
}
Your code works fine for me. (Although you have an Iterator you never use) However..
Maybe an easier way would be to do:
return (int) copy.stream().filter(e -> e > 0).count();
Which will filter out all the non positive numbers and then return the count of them. Also you can simply use the passed Collection:
public static int countPositive(Collection<Integer> collection) {
return (int)collection.stream().filter(e -> e > 0).count();
}
Which will eliminate the copy List and the extra variables.
Note that count() returns a long. If the size of collection might exceed the limit of an int, you will want to change the return type to long and not cast to an int.
You're not using the Iterator. Collections can automatically give you an iterator, so there's no need to convert it to an ArrayList to get this.
Iterator<Integer> itr = collection.iterator();
Now you actually have to use it. The for statement may look wonky to you as a beginner, but remember that a for statement starts with an optional initialization step, which can be omitted.
for(; itr.hasNext();) {
Integer value = itr.next();
// rest of your logic here
}
If you prefer, you can initialize the iterator in the for statement directly.
for(Iterator<Integer> itr = collection.iterator(); itr.hasNext();) {
Integer value = itr.next();
// rest of your logic here
}
In spite of the wasteful copying, the code appears to be fine. The critical part of the code is checking to see if there are positive values, and your code does accomplish that. This is simply making it cleaner and less ceremonious.

How do filter out this list with java 8 streams and functional interfaces?

if I have a list of arrays like this (pseudo java code):
Note the list valsSorted will be always sorted with x[0] asc and x[1] desc order.
List valsSorted = {[1 5][1 4][1 3][2 1][3 2][3 1][4 2][4 1][5 1][6 2][6 1]};
How do I filter this list with Java 8 streams and lambdas so that I get:
result = {[1 5][2 1][3 2][4 2][5 1][6 2]}
The first item of the array (x[0]) is ID and the second is a version number. So the rule is give all distinct IDs with the highest version back.
If I would use a for loop the following code would be fine:
ArrayList<int[]> result= new ArrayList();
int keep = -1;
for (int[] x : valsSorted) {
int id = x[0];
int version = x[1];
if(keep == id) continue;
keep = id;
result.add(x);
}
Your use of the word "distinct" suggests using the distinct() stream operation. Unfortunately that operation is hardwired to use the equals() method of the stream elements, which isn't useful for arrays. One approach for dealing with this would be to wrap the arrays in a wrapper object that has the semantics of equality that you're looking for:
class Wrapper {
final int[] array;
Wrapper(int[] array) { this.array = array; }
int[] getArray() { return array; }
#Override
public boolean equals(Object other) {
if (! (other instanceof Wrapper))
return false;
else
return this.array[0] == ((Wrapper)other).array[0];
}
#Override
public int hashCode() { ... }
}
Then wrap up your object before distinct() and unwrap it after:
List<int[]> valsDistinct =
valsSorted.stream()
.map(Wrapper::new)
.distinct()
.map(Wrapper::getArray)
.collect(toList());
This makes one pass over the data but it generates a garbage object per value. This also relies on the stream elements being processed in-order since you want the first one.
Another approach would be to use some kind of stateful collector, but that will end up storing the entire result list before any subsequent processing begins, which you said you wanted to avoid.
It might be worth considering making the data elements be actual classes instead of two-element arrays. This way you can provide a reasonable notion of equality, and you can also make the values comparable so that you can sort them easily.
(Credit: technique stolen from this answer.)
class Test{
List<Point> valsSorted = Arrays.asList(new Point(1,5),
new Point(1,4),
new Point(1,3),
new Point(2,1),
new Point(3,2),
new Point(3,1),
new Point(4,2),
new Point(4,1),
new Point(5,1),
new Point(6,2),
new Point(6,1));
public Test(){
List<Point> c = valsSorted.stream()
.collect(Collectors.groupingBy(Point::getX))
.values()
.stream()
.map(j -> j.get(0))
.collect(Collectors.toList());
for(int i=0; i < c.size(); i++){
System.out.println(c.get(i));
}
}
public static void main(String []args){
Test t = new Test()
}
}
I decided to use the point class and represent the ID field as x and the version number as Y. So from there if you create a stream and group them by ID. You can call the values method which returns a Collection of Lists Collection<List<Point>>. You can then call the stream for this Collection and get the first value from each list which according to your specifications is ordered with descending version number so it should be the the highest version number. From there all you have to do is collect them into a list, array or whatever you see necessary and assign it as needed.
The only problem here is that they are printed out of order. That should be an easy fix though.

Categories

Resources