2

I frequently use cfloop over an array or list because I have complex computations involving the values of the array. However, there doesn't seem to be a way to refer back to a specific member of the array/list. If I use an ordinary loop I can easily do that:

<cfloop from = "1" to = "#ArrayLen(myarray)#" index = "i">
<cfset temp = "myarray[i]">
<cfif mystruct[temp] GT 5>
  ... do something
</cfif>
<cfif myarray[i] NEQ myarray[i-1]>
  ...do something
</cfif>
</cfloop>

but the computations involvng myarray[i] can be cumbersome when I must refer to them by "i" instead of by the value at i.

But if I use a loop over the array, I often have to add a counter:

<cfset m = 0>
<cfloop array = #myarray# index = "value">
<cfset m = m + 1>
<cfif mystruct[value] GT 5>
 ... do something
</cfif> 
<cfif myarray[i] NEQ myarray[i-1]>
  ...do something
</cfif>
</cfloop>

Does anyone know how to refer to myarray[i-1] within the cfloop array = myarray coding? In researching this I found this rant: http://www.markdrew.co.uk/blog/post.cfm/cfloop-rant , but it offers no solutions.

Betty Mock
  • 1,373
  • 11
  • 23
  • 3
    use your own counter like your 2nd example, but change `m` to `i`. Be mindful that `i-1` might yield `0` (CF index is 1-based, 0 is out of bound), so test for edge case. – Henry Jun 13 '13 at 17:44
  • 2
    I usually just use your first solution when having to bounce around in relation to the current index like you're suggesting and use `cfloop array = ""` for basic loops. is there any reason you're trying to avoid an index loop? – genericHCU Jun 13 '13 at 17:54
  • sorry about the m - i disconnect – Betty Mock Jun 14 '13 at 18:15
  • Would need a counter if `myArray` is 2D or more and looping on array rather than counter to reference the elements `#idxTempArray[2]#......do after something 5th element...`. (Note the `cnt++` only works CF11 and later, is both compare and *then* increment, but it saves a line of code devoted just to incrementing. Pre CF11, `...`) – gordon Apr 28 '20 at 15:48

5 Answers5

3

Started this as a comment but quickly became too long. I typically use a counter as you mentioned. I don't think there is a built-in way for ColdFusion to handle this for you. If you are only worried about comparing the current value to the previous value, I have done something like this before:

<cfset previousValue = "">
<cfloop array = #myarray# index = "value">
    <cfif mystruct[value] GT 5>
        <!--- do something --->
    </cfif> 
    <cfif value NEQ previousValue>
        <!--- do something --->
    </cfif>
    <cfset previousValue = value>
</cfloop>
Miguel-F
  • 13,450
  • 6
  • 38
  • 63
3

You've identified a shortfall in ColdFusion's implementation of array looping: the index / position in the array simply isn't available.

I've raised this with Adobe, and also the similar failing in the arrayEach() function.

There is some "interesting" reading on this whole notion on Andrew Scott's blog, where various opinions (and differences therein) are discussed.

Incidentally Railo does this all properly, as I write-up on my own blog.

The bottom line is that you need to maintain your own counter, as you allude to, and @Miguel-F confirms.

Vote for the bugs... maybe Adobe'll fix 'em in ColdFusion 11...

Adam Cameron
  • 29,677
  • 4
  • 37
  • 78
  • Looks like they finally implemented it in 2016: https://tracker.adobe.com/#/view/CF-3341256 – Leigh Feb 06 '17 at 20:51
1

Let me try to fix your 2nd example and make it more readable...

<cfloop from="1" to="#ArrayLen(myarray)#" index="i">
  <cfset value = myarray[i]>
  <cfif mystruct[value] GT 5>
    ... do something
  </cfif>
  <cfset lastIndex = i - 1>
  <cfif lastIndex AND value NEQ myarray[lastIndex]>
    ... do something
  </cfif>
</cfloop>
Henry
  • 32,689
  • 19
  • 120
  • 221
  • I think I'd name the var `prevIndex` rather than `lastIndex` so as not to momentarily confuse myself as to it being the last element of the array. And if you don't want to set a var that you will use only once... `` Very nice rewrite, with i-1 as a boolean. – gordon Apr 28 '20 at 15:58
0

Let's think about this differently. If we rewrite these in cfscript for illustrative purposes:

  myArray=[ 'xyz', 'yyy', '232', 'uoiu', 'youneedthis', '2343'];
    writedump(myArray);
    myLongStringThing = '';

    // LOOP OVER AN INDEX AND MANUALLY PUSH YOUR ARRAY ALONG
    for ( i = 1; i <= myArray.size(); i++) {
      v = myArray[i];
      if(i < 5 && i > 1 && myArray[i] != myArray[i-1]   ){
        x = doSomethingComplicated(v);
        myArray[i] = x; // Your new computed value
      }

      if(i > 1 && v != myArray[i-1] ) {
        y = somethingElseCool(v);
        myLongStringThing &= y;
      }
    }

    writedump(myArray);
    writeoutput(myLongStringThing & '<br>');

    // LOOP THE VALUES IN THE STRUCT/ARRAY 
    i = 0;
    for (k in myArray) {
      i++;
      writeoutput('k:' & k & '<br>');
      writeoutput('a[i]:' & myArray[i] & '<br>');
    }

    function doSomethingComplicated(v) {
      Arguments.v &= 'you did it!';
      return Arguments.v;
}

    function somethingElseCool(v) {
      Arguments.v &= ';';
      return Arguments.v;
    }

    // LOOPING A STRUCT IS DIFFERENT
    myStruct = {'asdf' = 234234, 'sdfsd' = 9798, 'oiujlkj3' = 'kohjkjh'};
    for (k in myStruct) {
      writeoutput('kinstruct:' & k & '<br>');
      writeoutput('kinstruct:' & myStruct[k] & '<br>');
    }

If we consider the attribute signature of cfloop as the same as using for(counter;condition;incrementor) vs. for(valuekey in object) we see they are roughly the same thing.

You can choose how to iterate over your object (array) but you can't choose the value only version and have the current iterators keep track of the index for you.

So if you need to track your prev/next index, you need to choose an iterator method that allows an index (even if it is manually done).

NB: For an array, you can probably mix/match a keyvalue with a manual iterator (the for(k in obj) style) but when a structure is used, it is the actual KEY name that is returned into 'k'. Not the value of the iterated array. This is in line with the cfloop attribute signature of looping over a collection (eg: structure).

I think you are asking for the cfloop to let you set both a value and index attribute marker. And I realize other languages (and dialects) might do this, but so far in CF, nope.

The edge cases are easily manageable with a compound if statement.

You can use the exact same compound logic in the if statements in tags to account for the edge cases as well.

You may have noticed I used size() instead of ArrayLen(). There are a few java operators you can use on arrays. However, none that give you the 'cursor' or index of a given state as far as I can tell.

If you want to delve into arrays a bit more, you can create a java version of the array and do a few more things:

myArray = CreateObject( "java", "java.util.ArrayList" );
writeDump(myArray);  // This will show you the java object, which needs to be 'inited' still.
myArray = myArray.init(); // It returns the array, it does not act on the orig obj.
// You can now access the vast majority of java methods (including the parent methods)
myArray.add('myFirstValue');
myArray.add('my2ndValue');
myArray.add('yetAnotherValue');
writeDump(myArray);// This will look like a regular array dump
// You can also apply regular CF functions to this array like arrayAppend().
x = myArray.get(1); // This is a java index, so it begins at 0 which is the 1st elem. 1 is 2nd, etc.
writeOuput('At java index 1 we have:' & x & '<br>');

You can play with a few of those but none of them will return a usable cursor or index as far as I can tell. A few might be handy though, like subList() and equals().

Ultimately, if you want to find the index without keeping a manual index, you can do this:

index = arrayFind(myArray, v)

Which of course is just a look-up of the index by value, assuming the value is unique. Using one of the loop methods that uses an index seems safer rather than trying to figure out where you are after the fact.

williambq
  • 1,125
  • 7
  • 12
0

If you're on CF 10 or Railo 4, then you could use each() from the Underscore.cfc library to loop with an index:

_ = new Underscore();

_.each(myArray, function (value, index) {
   // loop code here
});

I'd suggest using a more expressive function like map() or reduce(), though, depending on what you're trying to accomplish with the loop.

Note: I wrote Underscore.cfc

Russ
  • 1,931
  • 1
  • 14
  • 15