2

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?

5
  • 3
    List<Integer> copy = new ArrayList<>(orig); copy.remove(i);? Commented Feb 21, 2022 at 23:26
  • I can think of several alternatives, but honestly they wouldn't be much of an improvement. What you have is not that bad. Commented Feb 21, 2022 at 23:49
  • Note that you can replace j -> nums.get(j) with nums::get. Commented Feb 21, 2022 at 23:50
  • You can keep track of the index by using an AtomicInteger instance. You start by instantiating such an instance with default value 0 ( for example new AtomicInteger(0)). Then you call getAndIncrement() on the instance from somewhere inside your stream. This will give you the current index and it also automatically increments the index by 1. So next time you call the method you'll get the updated index number. Commented Feb 22, 2022 at 0:06
  • See Stream#skip in this Answer. myList.stream().skip(1).filter( somePredicate ).toList() Commented Feb 22, 2022 at 1:18

2 Answers 2

8

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

1 Comment

Thanks for adding the performance note for new ArrayList<>(list); result.remove(i);. While it's the least verbose approach it's important to note it has performance implications.
0

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.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.