17

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);
5
  • 8
    This is incredibly easy using a for loop and remove(int). Don't use streams for everything. Commented Mar 29, 2016 at 20:05
  • 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 streams Commented Mar 29, 2016 at 20:07
  • 6
    Streams and any kind of indexing don't get along well at all. Commented Mar 29, 2016 at 20:07
  • 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); Commented Mar 30, 2016 at 10:00
  • @PaulBoddington I posted an answer using both, remove(int) and streams. Commented May 19, 2016 at 11:31

4 Answers 4

30

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());
Sign up to request clarification or add additional context in comments.

Comments

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.

Comments

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);

3 Comments

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.
@Holger Thanks! Integer::intValue what was I was looking for. I also tried Function.identity() but that did not work.
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…
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
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.