Ayoub
Ayoub

Reputation: 43

Remove elements from a List at a specific index

I am trying to program a method that deletes the first, second and third element of every group of 4 elements. It seems not working at all. Could anyone please help?

public static void reduziereKommentare(List<String> zeilen) {
        if (!zeilen.isEmpty()) {
            if (zeilen.size() % 4 != 0) {
                throw new RuntimeException("Illegal size " + zeilen.size() + " of list, must be divisible by 4.");
            }
            for (int i = 1; i <= zeilen.size() % 4; i++) {
                zeilen.remove(i);
                zeilen.remove(i + 1);
                zeilen.remove(i + 2);
            }
        }
        System.out.println(zeilen);
    }

Upvotes: 1

Views: 2842

Answers (4)

Kaan
Kaan

Reputation: 5784

Your code sample uses a data type of List - List<String> zeilen - but you separately wrote a comment which states that you're starting from an array:

"I used the Arrays.asList() function to add elements to the list"

The signature for asList() shows the input argument is an array, defined using varargs:

public static <T> List<T> asList(T... a)

Thus, you would start from something like this:

// rely on automatic array creation via varargs
List<String> list = Arrays.asList("one", "two", "three");

or from an explicit array, like this:

String[] strings = {"one", "two", "three"};
List<String> list = Arrays.asList(strings);

Here's a more complete picture of your current solution:

  • start with an array – String[] – creating it explicitly or relying on automatic array creation via varargs
  • create a List<String> from that array using Arrays.asList()
  • traverse the List skipping three items at a time, keeping only each fourth item (so: 4th, 8th, 12th, 16th, etc.)

Since the starting point is a String array, and knowing that you're interested in keeping only every 4th element, you could:

  • create a new, empty java.util.List<String>
  • iterate over each element of the array
  • for every 4th, 8th, etc element, add that to the final result list; ignore everything else

Here's the code to do that:

private static List<String> buildListOfEveryFourthElement(String[] array) {
    List<String> everyFourthElement = new ArrayList<>();
    if (array != null) {
        // start from "1", a bit easier to reason about "every 4th element"?
        int current = 1;
        
        for (String s : array) {
            if (current > 1 && current % 4 == 0) {
                everyFourthElement.add(s);
            }
            current++;
        }
    }
    return everyFourthElement;
}

I omitted the check for whether the input is exactly divisible by 4, but you could easily edit the first if statement to include that: if (array != null && array.length % 4 == 0) { .. }

A benefit to this "build the List as you go" approach (vs. calling Arrays.asList() with a starting array) is that the original input array would not be associated in any way with the result list.

So what? As you mentioned in one of your comments that you discovered it's not permissible to modify the list – calling .remove() will throw java.lang.UnsupportedOperationException. Note this will also happen if you try to add() something to the list.

Why does it throw an exception? Because asList() returns a java.util.List which is backed by the input array, meaning the list and array are sort of tied together. If it allowed you to remove (or add) items from (or to) the list then it would also have to automatically update the backing array, and they didn't implement it that way. Here's a brief snip from asList() Javadoc:

Returns a fixed-size list backed by the specified array. (Changes to the returned list "write through" to the array.)

By creating a new List and populating it along the way, you are free to modify that list later in your code by removing or adding elements, sorting the whole thing, etc. You would also be guarded against any changes to the array showing up as (possibly surprising) changes in the list – because the list is backed by the array, a change in an array element would be visible in the associated list.

Upvotes: 1

WJS
WJS

Reputation: 40062

Would it not be easier to simply add every fourth item to a new list and return that? This would also eliminate any repetitive copying that could be involved when removing elements from a list. And the target list can be appropriately sized to start.

public static List<String> reduziereKommentare(List<String> zeilen) {
    Objects.requireNonNull(zeilen);
    List<String> zeilen1= new ArrayList<>(zeilen.size()/4);

    for(int i = 3; i < zeilen.size(); i+=4) {
            zeilen1.add(zeilen.get(i));
    }

    return zeilen1;
}

You could also use a stream.


zeilen = IntStream.iterate(3, i ->i < zeilen.size(), i->i+=4)
                .mapToObj(zeilen::get).toList();

Notes:

  • whether the list is empty or the size is not divisible by 4, this will work. It will just ignore the extra elements.
  • assigning the result to the original variable will result in the old list being garbage collected.
  • I only check for a null argument since that would cause an exception. Of course, if alerting the user of the size is important just add the other check(s) back in.

Upvotes: 1

Computable
Computable

Reputation: 1471

My take...does not require the size precondition check but you may want to still catch that if it represents an error of broader scope than this method.

Given this test code...

    // Test code
    List<String> myList = new ArrayList<>();
    for (int i = 0; i < 20; i++) {
        myList.add(String.valueOf(i));
    }
    

the 'zeilen' loop can be implemented as ...

    // "before" diagnostics
    System.out.println(zeilen);

    // The 'zeilen' loop
    for (int i = 0, limit = zeilen.size(); i < limit; i++) {
        if ((i+1) % 4 > 0) zeilen.remove(i/4);
    }

    // "after" diagnostics
    System.out.println(zeilen);

and produces

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
[3, 7, 11, 15, 19]

Works with any length list leaving every '4th' element in list.

A few more test cases :

Given                   Results in
[]                      []
[0,1]                   []
[0,1,2,3]               [3]
[0,1,2,3,4]             [3]
[0,1,2,3,4,5,6,7]       [3,7]
[0,1,2,3,4,5,6,7,8]     [3,7]

Upvotes: 3

Rob Spoor
Rob Spoor

Reputation: 9135

As said in the comments, removing an element impacts the indexing. Whenever I need to do something like this, I either use an Iterator, or loop backwards.:

for (int i = zeilen.size() - 4; i >= 0; i -= 4) {
    zeilen.remove(i + 2);
    zeilen.remove(i + 1);
    zeilen.remove(i);
}

Note that I subtract 4 from i each iteration, so I go back a full block of four each time.

Also note that I remove the largest indexed elements first. If I use i, i + 1 and i + 2 inside the loop, I again run into the same issue. I could also have used i 3 times, but this makes it more clear.

Upvotes: 3

Related Questions