Home > Mobile >  Java - cleanest way to iterate over a stream keeping track of index
Java - cleanest way to iterate over a stream keeping track of index

Time:02-22

I'm trying to come up with a clean way to copy all elements of an ArrayList but one, based on its index.

In JavaScript we can filter by value, but also based on index as well. So what I'm trying to achieve will look something like that:

// nums is []
for(let i = 0; i <nums.length; i   {
   let copyNums = nums.filter((n, index) => index !== i);
}

In Java best I could do so far is this, which is super long and verbose. Not to mention the I couldn't use i itself as it's not final otherwise I'm getting

Variable used in lambda expression should be final or effectively final

       // nums is ArrayList
       for (int i = 0; i < nums.size(); i  ) {
            final int index = i;
            List<Integer> allElementsWithoutCurr = IntStream.range(0, nums.size())
                    .filter(j -> j != index)
                    .mapToObj(j -> nums.get(j))
                    .collect(Collectors.toList());
        }

Surely there is a better way to achieve this in Java?

CodePudding user response:

The simple way

List<Foo> result = new ArrayList<>(list);
result.remove(i);

For long lists and low values of i, this might be a bit slow because it has to shift the tail elements left, however for brevity and clarity you can't beat it.

The ugly way

You can use a stream and keep track of the index by using AtomicInteger, whose reference is effectively final, but whose value may be changed:

AtomicInteger index = new AtomicInteger();
List<Foo> result = list.stream()
  .filter(x -> index.getAndIncrement() != i)
  .collect(toList());

For large lists this may be faster since no shift left is required, and you can do other stuff in the stream in the one operation.

Of course if you want to filter many elements based on their index, you can do that without any performance hit.

With a stream, you might not even need the list if you just want to do stuff with the result directly:

list.stream()
  .filter(x -> index.getAndIncrement() != i)
  .forEach(foo -> {doSomething with foo});

CodePudding user response:

You can do it like this but it's rather ugly.

List<Integer> list = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9 );

int skip = 3;
List<Integer> result = new ArrayList<>(list.subList(0,skip));
result.addAll(list.subList(skip 1, list.size()));

System.out.println(result);

prints

[1, 2, 3, 5, 6, 7, 8, 9]

You could also employ System.arrayCopy to specify ranges. But you would still have to do multiple copies. If you have a big array, copying it and then removing the value might not be efficient (e.g. ArrayLists are are random access and values can't easily be deleted. LinkedList values can easily be deleted but you have to count up or back to them first.) So however you do it, partial copying (imo) would be the way to go. Nothing is wrong with the way you are currently doing it.

  •  Tags:  
  • java
  • Related