CFSCRIPT List Loop?

Ever wanted to do a list loop inside a CFSCRIPT block?  I mean,
you can already loop over an array or a structure just as easily as you
can with tags, why not a list?  Well, for some reason Macromedia
decided that capability wasn't particularly important, but you can work
around it using function variables.

runningTotal = 0;
listLoop("1,2,3", loopBody);
function loopBody(item) {
runningTotal = runningTotal + item;
writeOutput("Running Total: #runningTotal#
");
}

This code will loop over a the list "1,2,3", calculating and
outputting a running total as it goes.  It's not amazingly
elegant, because there isn't actually a for or while keyword, and the
loop "body" is in a separate function, but it is a simple loop over a
list in CFSCRIPT.  The magic 'listLoop' function is here:

function listLoop(myList, bodyFunction) {
var a = listToArray(myList);
var i = "";
for (i = 1; i LTE arrayLen(a); i = i + 1)
bodyFunction(a[i]);
}

It's pretty simple, just a quick dynamic function call.  This
type of programming is usually found in JavaScript (and related
languages) where functions are nothing more than a data type.  CF
doesn't quite meet that spec, because it forces you to declare
functions explicity (unlike other variable types), and doesn't provide
a function literal syntax (along with the omitted array and structure
literal syntaxes).  However, it's still an effective code trick
for certain scenarios.

12 responses to “CFSCRIPT List Loop?”

  1. Raymond Camden

    I must be confused. Why wouldn't you simply use a for loop that goes from 1 to listLen of the list? Sure you have to do a listGetAt to get the item, but this UDF seems like overkill, and your post makes it seem like you _have_ to do this, which isn't exactly true. I think I'd edit it to make that clear (imho).

  2. Niklas Richardson

    …also I wouldn't bother doing the listToArray() as that's an extra bit of work. Just loop over the list using the way Ray said.

    for (i = 1; i lte listlen(myList); i = i + 1)
    {
    item = listgetat(myList, i);
    …..
    }

  3. Barney

    Ray: you're definitely correct. This is superfluous and unnecessary, since you can do an index loop and dereference the list indexes sequentially. But that's not really a list loop. ; ) Rest assured that I'm not actually using this technique in any of my 'real' code, but it's kind of a neat trick that could be applied to other problems that don't have readily available alternate solutions.

    Niklas: Dereferecing an array is faster than dereferecing a list, particularly when the list is long. Each list operation has to tokenize the list, while an array is already broken down. So by doing the tokenization onces (the listToArray call), I save a bunch of time. I did some quick test, and with just a couple elements, the list operations were quicker. By the time you had 10 elements, the array method took about 50% of the time, and when you got to 20 elements, the array method took just under 10% of the time. So for lists that have more than a few elements, converting to an array first will be quicker.

  4. Niklas Richardson

    Barney,

    I understand that looping over an array is quicker than looping through a list.

    However, in simple loops (as above), it was my understanding that if you were to convert the list to an array and then loop over the array you would not be increasing performance as some work needs to happen to convert that list to array in the listToArray() function anyway. So the performance saving is negligable.

    This understanding was also confirmed by the ColdFusion MX Coding Guidelines and specifically this bit:

    "Don't slavishly convert lists to arrays

    Even though manipulating an array is generally faster than manipulating a list in CFMX, if you simply need to iterate over a list of items and process each one in turn the faster construct is … . Don't convert itemList to an array and then loop over that – it's not worth it because it probably won't be faster."

    http://livedocs.macromedia.com/wtg/public/coding_standards/performance.html

    Towards the end of the article.

    And:

    "Use arrays instead of lists – in general

    In CFMX, lists suffer from the generally slow string processing in Java which means that list manipulation can be slower than in CF5. In general, it is better to work with arrays of items instead of lists of items: listGetAt() is not an efficient way to work with individual items in a data set! However, see the list vs array caveat in the Don't section below."

    Regards

    Niklas

  5. Niklas Richardson

    Oh, it lots some code from the quote above! It should be:

    "Don't slavishly convert lists to arrays

    Even though manipulating an array is generally faster than manipulating a list in CFMX, if you simply need to iterate over a list of items and process each one in turn the faster construct is <cfloop list="#itemList#" index="x"> … </cfloop>. Don't convert itemList to an array and then loop over that – it's not worth it because it probably won't be faster."

  6. Barney

    Niklas,

    Yeah, for what I was doing, a CFLOOP LIST="" would be perfect, but as the title of the post indicates, I was looking for CFSCRIPT-based version of that loop. As the MM coding guidelines indicate, listGetAt is inefficient, and so I avoided it by using an array. Using a CFLOOP tag was impossible, because it can't be run in CFSCRIPT.

  7. Niklas RIchardson

    I see your point.

    It would be interesting to compare the performance of a for loop with listToArray() and a for loop with listGetAt() for 100, 1000 items.

    Maybe I'll throw something together if I have some time.

    Regards

    Niklas

  8. Niklas Richardson

    Now, these results are very interesting.

    Basically, as you said, performing a ListToArray() first and looping over the Array is MUCH quicker than my original suggestion.

    My only excuse is that I extrapolated quote from the performance doc to also mean "for" loops (for some reason???).

    In any case, I created a test that created a list with 10,000 items with randomly generated data that varied in length between 5 and 15 characters.

    I kept the test very simple and merely looped over the list (or array) and set a variable to the item value.

    The results are quite staggering.

    With a list of 10,000 items these are the results running on CFMX7 on a P4 2Ghz with 1GB Ram:

    List Time: ~32,500 ms
    Array Time: ~0 ms (max 15 ms)

    Perhaps an additional line needs to be added to that coding standards docs about lists in CFSCRIPT!! ;)

    I'm posting this code on our blog.

    Thanks for the challenge! ;)

    Regards

    Niklas

  9. Barney

    Yeah, I posted some similar test results in an earlier comment. With a few elements, the list is faster, but by the time you have even 10 elements, the array takes a fraction of the time.

    I'd still recommend a CFLOOP LIST="" loop if you're using tags, but if you're using script (where you don't have a native list loop), converting to an array is almost always a good idea.

  10. sher

    internet advertising internet advertising free advertising free advertising advertising specialties advertising specialties home depot home depot work at home work at home home based business home based business internet marketing internet marketing network marketing network marketing search engine marketing search engine marketing better business bureau better business bureau home based business home based business business cards business cards shopping cart shopping cart ecommerce shopping cart ecommerce shopping cart shopping cart software shopping cart software house plans house plans white house white house full house full house

  11. Robert, ecommerce pro

    I also think it's unnecessary. So why should one bother?

  12. Denstir

    Why bother? Thick as a brick comes to mind. Does no one actually _read_ what they are commenting on? Not that I don't suffer from the same malady, but SHEESH.

    Why? Maybe for refactoring? Maybe you are repeating your code, and realize, "hey, I can make a function that does this and save like 100000 lines of code!"? Maybe someone trying to grok the differences between JavaScript and Coldfusion functions and whatnot?

    I don't know and it doesn't matter. Read the damn blog before you post a damn question. GAAAH!

    :-P

    Just ribbin ya, Mr. ecommerce pro. Cuz uh felt like it. Nothing really wrong with questioning and all. :-)

    ***

    Nice little post Barney, some CF noob will no doubt find it useful. I liked the short exp. of the diff. between javascript funcs and cf funcs myself. :-)