Why you should iterate over list in reverse order if you remove items from list?

Let’s discuss today one thing which I believed to be quite obvious but for my client it sounded really obscure.

Assume you have some list of elements ( they may be of any type actually but we would assume they are integers for simplification ) and you need to iterate over them and remove some of them based on some criteria. In this post we would also assume that this criteria is just if an integer is less than five.

So generally speaking this topic relates to removal some elements by some criteria from a list with elements of any type, but for simplification purpose, we consider only case when we remove numbers which are less than five from an integer list.

An unsavvy attempt may be to write something like this


List<Integer> li = new List<Integer>();
for ( Integer i = 0; i < 10; i++ ) {
li.add( i );
}
System.debug(LoggingLevel.ERROR, '@@@ li.size(): ' + li.size() );
System.debug(LoggingLevel.ERROR, '@@@ li: ' + li );
for ( Integer i = 0; i < 10; i++ ) {
if ( li[i] < 5 ) { // System.ListException: List index out of bounds: 7
li.remove( i );
}
}
System.debug(LoggingLevel.ERROR, '@@@ li.size(): ' + li.size() );
System.debug(LoggingLevel.ERROR, '@@@ li: ' + li );

However, obviously this wouldn’t work. If you run this code in Salesforce, you would receive an error like System.ListException: List index out of bounds: 7.

Why would you get errors like this?

If you add debug statements you will find out instantly:


List<Integer> li = new List<Integer>();
for ( Integer i = 0; i < 10; i++ ) {
li.add( i );
}
System.debug(LoggingLevel.ERROR, '@@@ li.size(): ' + li.size() );
System.debug(LoggingLevel.ERROR, '@@@ li: ' + li );
for ( Integer i = 0; i < 10; i++ ) {
System.debug(LoggingLevel.ERROR, '@@@ i: ' + i );
System.debug(LoggingLevel.ERROR, '@@@ li[i]: ' + li[i] );
if ( li[i] < 5 ) { // System.ListException: List index out of bounds: 7
li.remove( i );
System.debug(LoggingLevel.ERROR, '@@@ li.size(): ' + li.size() );
System.debug(LoggingLevel.ERROR, '@@@ li: ' + li );
}
}
System.debug(LoggingLevel.ERROR, '@@@ li.size(): ' + li.size() );
System.debug(LoggingLevel.ERROR, '@@@ li: ' + li );

In debug statements you may find that size of list, namely, li.size() is 7 and you are trying to get 8-th element with index 7, which yields exception List index out of bounds: 7.

For me it is very obviously. When you are removing items from the list, list size changes and assumption that it will be ten during the iteration is wrong.

Let’s say you have figured this out and understood and you have replaced the seventh line with line


for ( Integer i = 0; i < li.size(); i++ ) {

and you execute the code and no exception will be thrown in this case but when you final the final list items you might get confused when you see that your list consists of (1, 3, 5, 6, 7, 8, 9). Why elements 1 and 3 were not removed? Aren’t they less than five?

The answer is yes, they are, but since you were iterating in ascending order and haven’t decrease counter these items have been just skipped. Check debug logs: item with index 1 is two. Since you have removed zero, the list has been shifted to beginning and items have been reindexed.

Items: 0 1 2 3 4 5 6 7 8 9

Indices: 0 1 2 3 4 5 6 7 8 9

Items: 1 2 3 4 5 6 7 8 9

Indices: 0 1 2 3 4 5 6 7 8

So when the control goes to the element with index 1, it takes two, not one, and one is just skipped. Like three which is also skipped.

There are two possible ways to fix this: you might either add decrement loop counter on line twelfth like this

 
List<Integer> li = new List<Integer>();
for ( Integer i = 0; i < 10; i++ ) {
li.add( i );
}
System.debug(LoggingLevel.ERROR, '@@@ li.size(): ' + li.size() );
System.debug(LoggingLevel.ERROR, '@@@ li: ' + li );
for ( Integer i = 0; i < li.size(); i++ ) {
System.debug(LoggingLevel.ERROR, '@@@ i: ' + i );
System.debug(LoggingLevel.ERROR, '@@@ li[i]: ' + li[i] );
if ( li[i] < 5 ) { // System.ListException: List index out of bounds: 7
li.remove( i );
i--;
System.debug(LoggingLevel.ERROR, '@@@ li.size(): ' + li.size() );
System.debug(LoggingLevel.ERROR, '@@@ li: ' + li );
}
}
System.debug(LoggingLevel.ERROR, '@@@ li.size(): ' + li.size() );
System.debug(LoggingLevel.ERROR, '@@@ li: ' + li );
 

or you may iterate over list in reverse order like this

List<Integer> li = new List<Integer>();
for ( Integer i = 0; i < 10; i++ ) {
li.add( i );
}
System.debug(LoggingLevel.ERROR, '@@@ li.size(): ' + li.size() );
System.debug(LoggingLevel.ERROR, '@@@ li: ' + li );
for ( Integer i = 9; i >= 0; i++ ) {
System.debug(LoggingLevel.ERROR, '@@@ i: ' + i );
System.debug(LoggingLevel.ERROR, '@@@ li[i]: ' + li[i] );
if ( li[i] < 5 ) { // System.ListException: List index out of bounds: 7
li.remove( i );
System.debug(LoggingLevel.ERROR, '@@@ li.size(): ' + li.size() );
System.debug(LoggingLevel.ERROR, '@@@ li: ' + li );
}
}
System.debug(LoggingLevel.ERROR, '@@@ li.size(): ' + li.size() );
System.debug(LoggingLevel.ERROR, '@@@ li: ' + li );

You may check that in both cases your final list consists of elements which are bigger or equal to five: (5, 6, 7, 8, 9).
I would personally advice to everybody to use always iteration in reverse ( descending ) order when you need to remove items from a list.
Since in this way you are less likely to make a mistake and you don’t have to use additional increment operator, since when you iterate over a list in reverse order, only processed part of list may shift but not the part which you haven’t processed yet.

Enjoy, don’t worry and be happy.
You may also click like if you really liked this post.
May be Force.com with you.

Advertisements
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

w

Connecting to %s