1

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?

Donal Fellows
  • 133,037
  • 18
  • 149
  • 215
Anton Belev
  • 11,963
  • 22
  • 70
  • 111
  • 3
    `List copy = new ArrayList<>(orig); copy.remove(i);`? – Shawn Feb 21 '22 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. – shmosel Feb 21 '22 at 23:49
  • Note that you can replace `j -> nums.get(j)` with `nums::get`. – shmosel Feb 21 '22 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. – Maurice Feb 22 '22 at 00:06
  • See `Stream#skip` in [this Answer](https://stackoverflow.com/a/43121966/642706). `myList.stream().skip(1).filter( somePredicate ).toList()` – Basil Bourque Feb 22 '22 at 01:18

2 Answers2

6

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});
Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • 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. – Anton Belev Feb 22 '22 at 10:08
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.

WJS
  • 36,363
  • 4
  • 24
  • 39