Looping Over a Changing Collection

 

 

General Recommendation: use foreach

When iterating over a full collection, the best practice is to use the foreach syntax. It is faster and avoids the problems detailed below. See Control StructuresLesson 10: Scheduled Functions, Flow Control, Basic Messaging, or the Quick Reference for how to use foreach.

 

 

 

Looping Backwards to Avoid Skipping Records

Consider the following DSL snippet:

region[] regions = region:empty(index);
region temp;
for (int i = 0; i < regions.length(); i++) {
	temp = regions.get(i);
	temp.index = region:get_index();
	temp.save();
}

If an item in a collection is changed in such a way that it does not satisfy the selector criteria that populated it in the first place, it is immediately removed from the collection. In the above example, looping forwards, it means an item is removed while the index is still incremented resulting in a skipped item. Looping backwards, however, means all items are still iterated over even if the current one is removed from the collection. So in this scenario, the following would be considered best practice to achieve what was intended above:

region[] regions = region:empty(index);
region temp;
for (int i = regions.length()-1; i >= 0; i--) {
	temp = regions.get(i);
	temp.index = region:get_index();
	temp.save();
}

 

 

 

Looping Backwards to Avoid Index out of Bounds Errors

Similar to the previous section, looping forwards over a collection while changing it can result in index out of bound errors. For example, referring to the topmost snippet, this would have happened if regions.length() was assigned to an int regionsLength beforehand and the for loop was written as for (int i = 0; i < regionsLength; i++). The solution here is also to loop backwards instead.

 

 

 

"Caching" your Query Result for Better Performance

Consider the following DSL snippet:

Children[] cs1 = /*backing selector*/
for (int i = 0; i < cs1.length(); i++) {
	/*loop code*/
}

 

If the size of the collection cs1 were to change due the loop code removing an item from the list (e.g. by updating an attribute which will exclude it from the selector's result), the selector (and the ensuing SQL query) will execute again to update the collection. If it's a complex selector resulting in a resource intensive SQL query, it might slow down performance significantly.

One way to handle this is to create a new, "static" list, copied from the dynamic list. For example, cs2 in the code below will not be refreshed, and iterating over it will not cause the cs1 backing selector te be re-executed for each iteration causing a collection size change:

Children[] cs1 = /*backing selector*/
Children[] cs2;
for (int i=0; i < cs1.length(); i++){
	cs2.append(cs1.get);
}
for (int c = 0; i < cs2.length(); c++){
	/*loop code*/
}

 

You can think of cs2 in the above snippet as a cached copy of cs1. Using static in addition to dynamic lists is, however, not considered to be best practice in all scenarios, and should be evaluated on a case by case basis. It's a good idea to "cache" your collection when most or all of the following apply:

  1. The likelihood of items being changed when looping over them, and thus the likelihood that the collection will need to be "refreshed" resulting in the selector being executed again, is high
  2. The backing selector is complex and will execute a resource intensive query
  3. The typical amount of data being processed or iterated over is high
  4. It's relatively quick to copy the collection contents to a different collection