Remove max (or min) from collection using Java8 streaming API
Asked Answered
D

3

11

I have little problem with code design that use new streaming API from Java 8. I would like to learn new things and one of the task is:

Reject max and min from list. List not contains duplicates.

Looks simple? Nope... My code:

  List<Integer> ranges = Lists.newArrayList(new Range(1, 15));
        List<Integer> collect = ranges.stream()
                .filter(x -> x != ranges.stream()
                        .mapToInt(Integer::intValue)
                        .max()
                        .getAsInt())
                .filter(x -> x != ranges.stream()
                        .mapToInt(Integer::intValue)
                        .min()
                        .getAsInt())

                .collect(Collectors.toList());
        assertThat(collect).hasSize(13);   // OK
        assertThat(collect).isEqualTo(Lists.newArrayList(new Range(2,14)));   // OK

this code is good (if only we dont have duplicates of min/max, but this is not a core problem here) but problem is that I use here three streams. First is main stream, second to remove max and third to remove min. Is there any possibility to do this task in one stream?

//edit: Very primitive Scala version:

val list = List.range(1, 15).sortWith(_>_).tail.reverse.tail

with additional sort because we could have shuiffeled list.

Dusa answered 7/4, 2014 at 11:25 Comment(7)
Uhm, doing this without streams is even more simple... But OK, this is an exercise, so...Traject
You can't do it with one stream as long it is unsorted. You need to resolve min and max beforehand. If your stream is sorted you can do it with with .filter(x -> x != ranges.get(0)).filter(x -> x != ranges.get(ranges.size()-1))Dorso
I think that the Scala version can not be compared to the Java version. The Scala approach (sorting and removing the first and the last element) is completely different to the Java approach (computing min and max, and filtering the stream)Lenny
@Lenny I know but it's a nice example what I want to get.Concavoconvex
Shouldn't something like List<Integer> collect = ranges.stream().sorted().collect(Collectors.toList()).subList(1, ranges.size()-1); then be an option? It's at least closer to the Scala approach...Lenny
@Dusa please keep in mind that sorting the collection and removing the first/last element will be in O(n*log(n)) while calculating min/max and then eliminating these with a stream will be O(n) (both worst case)Dorso
possible duplicate of Excluding extrema from HashSet with a StreamQuincentenary
W
7

The solution is not very efficient, but I think it follows your requirements - it does what you want and it is in one single pipeline - one single sequence of bulk data operations, how Java 8 calls it.

import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Stream<Integer> ranges = Stream.iterate(1, i -> i + 1).limit(15);
List<Integer> collect = ranges
    .sorted(Comparator.reverseOrder()) // sort the stream from the highest to the smallest
    .skip(1)                           // discards 1 element from the beginning
    .sorted()                          // sort the stream from the smallest to the highest
    .skip(1)                           // discards 1 element from the beginning
    .collect(Collectors.toList())     
    ;

But, as fge suggested and Marco13 wrote in their comment below your question, it would be better and much more efficient just to sort the stream, terminate the pipeline to a list and then remove the first and the last member :P Or even faster without sort - go through all the elements, find min and max, remember their position and then remove them.

Washboard answered 7/4, 2014 at 21:26 Comment(0)
T
29

Don't forget Collection.removeIf. You can compute min and max, and then do:

list.removeIf(x -> x == min || x == max);

(This also deals well with duplicates.)

Timoshenko answered 10/7, 2014 at 15:54 Comment(1)
Brilliant. I had no idea this existed!Theiss
W
7

The solution is not very efficient, but I think it follows your requirements - it does what you want and it is in one single pipeline - one single sequence of bulk data operations, how Java 8 calls it.

import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Stream<Integer> ranges = Stream.iterate(1, i -> i + 1).limit(15);
List<Integer> collect = ranges
    .sorted(Comparator.reverseOrder()) // sort the stream from the highest to the smallest
    .skip(1)                           // discards 1 element from the beginning
    .sorted()                          // sort the stream from the smallest to the highest
    .skip(1)                           // discards 1 element from the beginning
    .collect(Collectors.toList())     
    ;

But, as fge suggested and Marco13 wrote in their comment below your question, it would be better and much more efficient just to sort the stream, terminate the pipeline to a list and then remove the first and the last member :P Or even faster without sort - go through all the elements, find min and max, remember their position and then remove them.

Washboard answered 7/4, 2014 at 21:26 Comment(0)
M
0

As the other answers have shown, Java can do this just fine. To provide an alternative to the other answers, you can make this even more concise if you know the size of the data set beforehand. To match your Scala version, here is the essentially the same thing in Java.

IntStream
   .rangeClosed(1,15)
   .sorted()               //Sorts the contents
   .limit(dataSetSize - 1) //Keeps all except the last element
   .skip(1)                //Removes the min element
   .boxed()
   .toList()
   ;

That gives you this.

[2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

You can replace the IntStream.rangeClosed(1, 15) with any Stream<T extends Comparable<T>>. As long as T extends Comparable<T>, then this strategy works.

And if T does not implement Comparable<T>, just provide your own Comparator<T>.

record Foo(int value) {}

Stream
   .of(new Foo(1), new Foo(7), new Foo(3), new Foo(2), new Foo(6), new Foo(4), new Foo(5))
   .sorted(Comparator.comparing(Foo::value))
   .limit(dataSetSize - 1)
   .skip(1)
   .toList()
   ;

That gives you this.

[Foo[value=2], Foo[value=3], Foo[value=4], Foo[value=5], Foo[value=6]]
Miry answered 9/9, 2023 at 22:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.