Java stream filter items of specific index
Asked Answered
E

4

16

I'm looking for a concise way to filter out items in a List at a particular index. My example input looks like this:

List<Double> originalList = Arrays.asList(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0);
List<Integer> filterIndexes = Arrays.asList(2, 4, 6, 8);

I want to filter out items at index 2, 4, 6, 8. I have a for loop that skips items that match the index but I was hoping there would be an easy way of doing it using streams. The final result would look like that:

List<Double> filteredList = Arrays.asList(0.0, 1.0, 3.0, 5.0, 7.0, 9.0, 10.0);
Ebarta answered 29/3, 2016 at 20:1 Comment(5)
This is incredibly easy using a for loop and remove(int). Don't use streams for everything.Vaginate
Thanks @PaulBoddington. Coming from .NET work, I'm used to using LINQ for most of the array/list operations and its easy to do this using LINQ so was hoping something similar would be in Java 8 streamsEbarta
Streams and any kind of indexing don't get along well at all.Hendel
These explicit array creation statements like new Double[] {…} and new Integer[] {…} are obsolete. Just use, e.g. List<Integer> filterIndexes = Arrays.asList(2, 4, 6, 8);Seda
@PaulBoddington I posted an answer using both, remove(int) and streams.Theall
L
27

You can generate an IntStream to mimic the indices of the original list, then remove the ones that are in the filteredIndexes list and then map those indices to their corresponding element in the list (a better way would be to have a HashSet<Integer> for indices since they are unique by definition so that contains is a constant time operation).

List<Double> filteredList = 
    IntStream.range(0, originalList.size())
             .filter(i -> !filterIndexes.contains(i))
             .mapToObj(originalList::get)
             .collect(Collectors.toList());
Lapstrake answered 29/3, 2016 at 20:7 Comment(0)
D
6

If your filteredIndexes list is presorted, you can avoid checking every element in this way:

List<Double> filteredList = IntStream.rangeClosed(0, filterIndexes.size())
    .mapToObj(idxPos -> idxPos == 0 
           ? originalList.subList(0, filterIndexes.get(idxPos)) 
           : idxPos == filterIndexes.size() 
           ? originalList.subList(filterIndexes.get(idxPos-1)+1, originalList.size()) 
           : originalList.subList(filterIndexes.get(idxPos-1)+1, filterIndexes.get(idxPos)))
    .flatMap(List::stream)
    .collect(Collectors.toList());

Here we create a number of sublists which contain all the elements between the filtered indices, then just flatten them into the single final list. For big input (e.g. a million of numbers) this solution could be magnitudes faster than one proposed by @AlexisC.

Draggletailed answered 30/3, 2016 at 7:53 Comment(0)
T
3

If you sort your indexes descending, then you can use java.util.List.remove(int) to remove the items.

List<Double> originalList = new ArrayList<>(Arrays.asList(0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0));
List<Integer> filterIndexes = Arrays.asList(2, 4, 6, 8);

filterIndexes.stream()

    // remove higher indixes first, because each remove affects all indexes to the right
    .sorted(Comparator.reverseOrder())

    // make sure to use remove(int) not remove(Object) in java.util.List to use indexes
    .mapToInt(Integer::intValue)

    // remove each index
    .forEach(originalList::remove);

// print results
originalList.forEach(System.out::println);
Theall answered 19/5, 2016 at 10:52 Comment(3)
Don’t use .mapToInt(Integer::valueOf). That works, but you are converting the Integer object to an int value to pass it to Integer valueOf(int) to get again an Integer object which is then auto-unboxed to an int as you are using mapToInt. You surely want either, .mapToInt(i->i) or .mapToInt(Integer::intValue), performing a single unboxing, instead.Seda
@Seda Thanks! Integer::intValue what was I was looking for. I also tried Function.identity() but that did not work.Theall
Yes, Function.identity() returns a Function rather than a ToIntFunction, so that doesn’t work as the conventional type compatibility rules apply here, rather than functional signature matching as when using x -> x directly…Seda
I
0

Probably you can use simple Iterator?

Stream<Integer> rows = Stream.of(1, 2, 3);
Iterator<Integer> it = rows.iterator();
int pos = 0;

while (it.hasNext()) {
    Integer num = it.next();

    if (pos++ == 0) {
        // this is a head
    } else {
        // this is a record
    }
}
Insnare answered 22/8, 2023 at 7:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.