1

I have a List and I'm looping through it removing items in the List if there is a match. I'm using i = -1 if the item in the List is removed. But it loops through from the beginning again. Is there a better way to do this?

private List<String> populateList(List<String> listVar) {
    List<String> list = new ArrayList<String>();
    list.add("2015-01-13 09:30:00");
    list.add("2015-01-13 06:22:12");
    list.add("2015-01-12 05:45:10");
    list.add("2015-01-12 01:52:40");
    list.add("2015-01-12 02:23:45");
    return list;
}

private void removeItems() {
    List<String> list = new ArrayList<String>();
    list = populateList(list);  
    System.out.println("List before modification : "+list);
    for (int i = 0; i <  list.size(); i++) {
        String dateNoTime = list.get(i).split(" ")[0];
        System.out.println("   Going over : "+list.get(i));
        if(!dateNoTime.equalsIgnoreCase("2015-01-13")) {
            System.out.println("      Removing : "+list.get(i));
            list.remove(i);
            i = -1; //This is making the loop start from scratch. Is there a better way?
        }   
    }
    System.out.println("List after modification: "+list+"\n\n");
}
Damien-Amen
  • 7,232
  • 12
  • 46
  • 75
  • am I missing something? why can't you just remove the part `i = -1`? – SOfanatic Jan 15 '15 at 17:56
  • Should be a concurrent modification exception!? Use an Iterator instead. – jp-jee Jan 15 '15 at 17:57
  • just loop in reverse direction. (from end to begining) – njzk2 Jan 15 '15 at 17:57
  • possible duplicate of [Iterating through a list, avoiding ConcurrentModificationException when removing in loop](http://stackoverflow.com/questions/223918/iterating-through-a-list-avoiding-concurrentmodificationexception-when-removing) – nbro Jan 15 '15 at 17:58
  • @SOfanatic : because the elements inside the list are removed and the list is altered. So the position of the item in the list has changed – Damien-Amen Jan 15 '15 at 17:59
  • This question is duplicate at least of 2 other questions: http://stackoverflow.com/questions/1921104/loop-on-list-with-remove?rq=1 – nbro Jan 15 '15 at 18:00
  • @jp-jee There is no `ConcurrentModificationException` here because he's using a `for` loop with an integer index. `ConcurrentModificationException` comes up when you have something like `for (String s : list)`. – ajb Jan 15 '15 at 18:22

6 Answers6

5

Java's List<T> provides a better way of removing items from it by using ListIterator<T>:

ListIterator<String> iter = list.listIterator();
while (iter.hasNext()) {
    String s = iter.next();
    String dateNoTime = s.split(" ")[0];
    if(!dateNoTime.equalsIgnoreCase("2015-01-13")) {
        iter.remove();
    }
}
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
1

You can use Iterator interface which has remove method.

    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext())
    {
        String next = iterator.next();
        String dateNoTime = next.split(" ")[0];

        if(!dateNoTime.equalsIgnoreCase("2015-01-13")) {
            System.out.println("      Removing : "+next);
            iterator.remove();
        }
    }
cool
  • 1,746
  • 1
  • 14
  • 15
1

With Java-8, you can simplify the entire thing to:

List<String> filtered = list.stream().filter(item -> item.split(" ")[0]
                                                         .equalsIgnoreCase("2015-01-13"))
                                                         .collect(Collectors.toList());

I think this is the shortest way to remove items from the list by checking them one-by-one. (I mean 'shortest' in terms of code size, not complexity-wise).

Chthonic Project
  • 8,216
  • 1
  • 43
  • 92
1

When you're using an index to iterate through a list, and you're removing items from the list, you need to be careful about how you handle the index. (Doing the remove through the iterator as in @dasblinkenlight's answer is better in this case, but there are other similar situations where it's not possible.)

Suppose you just deleted the line that resets i:

for (int i = 0; i <  list.size(); i++) {
    String dateNoTime = list.get(i).split(" ")[0];
    System.out.println("   Going over : "+list.get(i));
    if(!dateNoTime.equalsIgnoreCase("2015-01-13")) {
        System.out.println("      Removing : "+list.get(i));
        list.remove(i);
        //i = -1; //This is making the loop start from scratch. Is there a better way?
    }   
}

Now, when i==2, you decide you need to remove the element. When you do so, the item that was element 3 then becomes element 2, and the element that was element 4 then becomes element 3, and so on.

But then you go back up to the top and increment i. It is now 3. The result is that the element that was element 3, but is now element 2, is never examined at all. It gets skipped.

There are a couple ways to deal with this.

One is to make sure i doesn't get incremented. I've seen good programmers do things like this:

for (int i = 0; i <  list.size(); i++) {
    String dateNoTime = list.get(i).split(" ")[0];
    System.out.println("   Going over : "+list.get(i));
    if(!dateNoTime.equalsIgnoreCase("2015-01-13")) {
        System.out.println("      Removing : "+list.get(i));
        list.remove(i);
        i--;   // Move "i" backwards so that no elements are skipped
    }   
}

Personally, I dislike modifying an index inside a for loop like this, so I'd be happier with

int i = 0;
while (i < list.size()) {
    String dateNoTime = list.get(i).split(" ")[0];
    System.out.println("   Going over : "+list.get(i));
    if(!dateNoTime.equalsIgnoreCase("2015-01-13")) {
        System.out.println("      Removing : "+list.get(i));
        list.remove(i);
    } else {
        i++;   
    }   
}

[Note that in both cases, it's important to use list.size() in the termination condition, and not save the original value in a variable. The list size will change, and you want to use the new size when checking for termination.]

Another solution that may be appropriate in some cases is to go backward through the list:

for (int i = list.size() - 1; i >= 0; i--)
    String dateNoTime = list.get(i).split(" ")[0];
    System.out.println("   Going over : "+list.get(i));
    if(!dateNoTime.equalsIgnoreCase("2015-01-13")) {
        System.out.println("      Removing : "+list.get(i));
        list.remove(i);
    } 
}

which doesn't have the problem when elements are shifted.

ajb
  • 31,309
  • 3
  • 58
  • 84
0

Use Iterators.

Javadoc for Iterator says the following

Iterators allow the caller to remove elements from the underlying collection during the iteration with well-defined semantics.

Pandiri
  • 313
  • 1
  • 4
0

Or you can use guava API to achieve it.

FluentIterable
       .from(list)
       .transform(new Function<String, String>(){
           @Override
           public void apply(String input){
               return input.split(" ")[0];
           }
       }).filter(new Predicate<String>(){
           @Override
           public boolean apply(String input){
               return input.equalsIgnoreCase("2015-01-13");
           }
       }).toList();
Vivek
  • 1,640
  • 1
  • 17
  • 34