As the title suggests, this is part four of a series of articles I've written about arrays in CFML. So far I've covered what an array is and how to create them, plus two articles covering all the functions in CFML which specifically work with arrays.
As well as these functions that are specifically implemented to maintain & manipulate arrays, there are a bunch of other functions relating to other data types that use arrays. I'm going to cover those in this article. I'll break them down by the data-type they relate to. I'm not going to do indepth coverage of how the functions work, because I'll eventually cover them when I give the various other data types the same treatment I'm giving arrays here. EG: I'll discuss structFindKey() thoroughly when I do an exploration into "Structs in ColdFusion" (OK, so you've been fore-warned... there's more of this to come! ;-). I'm just gonna dwell on the array`ed-ness of the relevant functionality.
Strings
A few string-manipulation functions return arrays.REFind() / REFindNoCase()
Regexes are a topic into themselves (which I will cover at some point, but in the mean time you could read what Ben Nadel has to say on the matter), so I'll not go too much into them here, other then use some jargon. When doing a regex find, one can capture subexpressions as well as the main match. By default REFind() discards these subexpression matches and just returns the index of the starting point of the main match. However the fourth argument of REFind() allows one to return the subexpressions too.REFind(reg_expression, string [, start, returnsubexpressions ] )
When this argument is true, REFind() returns a struct instead of an integer, and the struct has keys pos and len, each of which are arrays wherein each element represents the position and length of a subexpression match (the first array element is the "main" match). EG:
regex = '<cffunction[^>]+returntype="(struct|array)"[^>]*>';
sample = '<cffunction name="getObjects" returntype="struct" access="public" output="false" hint="Returns the objects as a struct, keyed on ID.">';
matchResult = reFind(regex, sample, 1, true);
writeDump(matchResult);
functionSignature = mid(sample, matchResult.pos[1], matchResult.len[1]);
returnType = mid(sample, matchResult.pos[2], matchResult.len[2]);
writeOutput("Function signature: #htmlEditFormat(functionSignature)#<
");
writeOutput("Return type: #returnType#<
");
Result:
struct | |||||||
---|---|---|---|---|---|---|---|
LEN |
| ||||||
POS |
|
Function signature: <cffunction name="getObjects" returntype="struct" access="public" output="false" hint="Returns the objects as a struct, keyed on ID.">
Return type: struct
I think the structure of the return type here is quite odd. To me, it should be an array of structs, not a struct of arrays, eg:
array | |||||||
---|---|---|---|---|---|---|---|
1 |
| ||||||
2 |
|
One doesn't work with all the lengths, and then all the positions separately, one works with each match's pos & len. Still: that's a minor gripe. It's handy functionality though, that's for sure.
Obviously reFindNoCase() works the same as reFind().
REMatch() / REMatchNoCase()
Like reFind(), reMatch() does a regex find in a string, the difference being that it returns all matches found, not just the first one. On the other hand, it only returns the main match, and does not have the capability to return all the subexpression info as well. I think Adobe dropped the ball here. Returning all matches is a great idea... I almost always want all of them, not just the first one, but also I almost always want the subexpressions too. So we've got two functions (well: four, with the ~nocase versions), neither of which cover all the functionality. What Adobe should have done is just added a scope argument to reFind(), thus:
REFind(reg_expression, string [, start, returnsubexpressions, scope ] )Where the scope argument works the same as per replace() (etc), in that it specifies either "ONE" or "ALL". That would have been the sensible thing to do. I'd even go as far as to say that reMatch() / reMatchNoCase() should be deprecated in favour of this. I've raised an E/R for this: 3321666.
Anyway, we have them for the time being, so I'll clam-up about that and show you the array stuff. Both these functions returns an array of matches:
regex = '"[^"]+"';
sample = '<cffunction name="getObjects" returntype="struct" access="public" output="false" hint="Returns the objects as a struct, keyed on ID.">';
matchResult = reMatch(regex, sample);
writeDump(matchResult);
array | |
---|---|
1 | "getObjects" |
2 | "struct" |
3 | "public" |
4 | "false" |
5 | "Returns the objects as a struct, keyed on ID." |
We've got a list of attribute values. Here's a case in point of where the subexpressions would be handy... I want to capture the stuff between quotes, but I don't actually want the quotes. So it'd be great if reMatch() would respect subexpressions so I could match the bare values separately.
Lists
listToArray()
The only list function that interacts with an array (other than arrayToList() which I've already covered) is listToArray(). It's self-explanatory what this does: it takes a list and converts it to an array. Prior to ColdFusion implementing shorthand notation for creating arrays, this was the easiest way to get simple data into an array:
numbers = listToArray("tahi,rua,toru,wha,rima,ono,whitu,waru,iwa,tekau");
writeDump(numbers);
oddNumbers = listToArray("tahi,,toru,,rima,,whitu,,iwa,", ",", true);
writeDump(oddNumbers);
array | |
---|---|
1 | tahi |
2 | rua |
3 | toru |
4 | wha |
5 | rima |
6 | ono |
7 | whitu |
8 | waru |
9 | iwa |
10 | tekau |
array | |
---|---|
1 | tahi |
2 | [empty string] |
3 | toru |
4 | [empty string] |
5 | rima |
6 | [empty string] |
7 | whitu |
8 | [empty string] |
9 | iwa |
10 | [empty string] |
As demonstrated: this approach still had one benefit over short-hand notation: one can create a sparse array. It's not easy to achieve this with shorthand notation, because trying to do this:
evenNumbers = [, "rua", , "wha", , "ono", , "waru", , "tekau"];
writeDump(evenNumbers);
Yields a compiler error:
The following information is meant for the website developer for debugging purposes. | |||
Error Occurred While Processing Request | |||
|
Creating a sparse array is possible with shorthand notation, but it's clumsy:
evenNumbers = [javaCast("null", ""), "rua", javaCast("null", ""), "wha", javaCast("null", ""), "ono", javaCast("null", ""), "waru", javaCast("null", ""), "tekau"];
writeDump(evenNumbers);
(One might think one could tidy that up by doing this):
null = javaCast("null", "");
evenNumbers = [null, "rua", null, "wha", null, "ono", null, "waru", null, "tekau"];
But because of CF's inability to cope with NULL in CFML, this errors with this:
The following information is meant for the website developer for debugging purposes. | |
Error Occurred While Processing Request | |
|
I wish Adobe would just get on with it and add proper support for the concept of NULL in CFML (note: there is 3042895 to cover this, but it's closed "never fix". Nice).
Note that arraySort() is listed in the docs as a list function, but other than the fact the example code uses a list function, it's got nothing to do with lists.
Structs
structFindKey() / structFindValue()
Both of these look-up data from a struct, and return the results in an array. EG:numbers = {
maori = {
one = "tahi",
two = "rua",
three = "toru",
four = "wha"
},
japanese= {
one = "ichi",
two = "ni",
three = "san",
four = "shi"
}
};
ones = structFindKey(numbers, "one", "all");
rua = structFindValue(numbers, "rua", "all");
writeDump(var=numbers, label="data");
writeDump(var=ones, label="Struct keys that are 'one'");
writeDump(var=rua, label="struct elements with a value of 'rua'");
data - struct | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
JAPANESE |
| ||||||||||
MAORI |
|
Struct keys that are 'one' - array | |||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 |
| ||||||||||||||||||
2 |
|
struct elements with a value of 'rua' - array | |||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 |
|
The ins and outs of these functions are outwith the remit of this article, and to be frank the link to arrays is tenuous other than the return values, but they're handy and under-utilised functions, so I figured I'd include them. One good thing is that the owner key in the return result is a reference to the owner struct, not simply a copy of its value, so one can manipulate the underlying struct via the returned array. Handy.
structKeyArray()
structKeyArray() extracts the keys from a struct, representing them as an array. Structs don't store their keys in a guaranteed order, but sometimes one needs to access a struct's values according to some sense of key order (rather then the order of the values, for which one would use structSort()). structKeyArray() facilitates this. One can pull the keys out with structKeyArray(), sort the array, then use it as a key lookup. EG:letters = {
e = "echo",
a = "alpha",
b = "bravo",
d = "delta",
c = "charlie"
};
writeDump(var=letters, label="Under the hood, <cfdump> sorts them");
writeOutput("<hr />");
writeOutput("Struct keys not sorted by default:<
");
for (letter in letters){
writeOutput("#letter#: #letters[letter]#<
");
}
writeOutput("<hr />");
writeOutput("structKeyArray() not sorted by default:<
");
keys = structKeyArray(letters);
writeDump(keys);
writeOutput("<hr />");
writeOutput("Having sorted the array, we can access the struct keys in alphabetic order:<
");
arraySort(keys, "textnocase");
writeDump(keys);
It's important to remember that just as the keys in the struct don't have a guaranteed order, nor do the keys in the array returned from structKeyArray():
Under the hood, <cfdump> sorts them - struct | |
---|---|
A | alpha |
B | bravo |
C | charlie |
D | delta |
E | echo |
Struct keys not sorted by default:
D: delta
E: echo
A: alpha
B: bravo
C: charlie
structKeyArray() not sorted by default:
array | |
---|---|
1 | D |
2 | E |
3 | A |
4 | B |
5 | C |
Having sorted the array, we can access the struct keys in alphabetic order:
array | |
---|---|
1 | A |
2 | B |
3 | C |
4 | D |
5 | E |
structSort()
If one needs to sort a struct by its values, one uses structSort(). As mentioned above, structs don't (and can't) store their keys with a guaranteed sense of order, so when doing a structSort(), the struct itself isn't sorted or modified in any way. What happens with structSort() is that it returns the keys in the sequence of the ordered values via an array: because obviously an array does have a sense if ordering. So much like structKeyArray(), one does a structSort(), then loops over the ordered array to access the struct in that order:numbers = {
five = "rima",
four = "wha",
three = "toru",
two = "rua",
one = "tahi",
six = "ono"
};
writeOutput("Struct keys/values not sorted by default:<
");
writeDump(structKeyArray(numbers));
writeOutput("<hr />");
writeOutput("structSort() returns an array of keys according to the ordering of the key values:<
");
keys = structSort(numbers);
for (number in keys){
writeOutput("#number#: <strong>#numbers[number]#</strong><
");
}
writeOutput("(It's a bit odd to sort numbers alphabetically by their name, but it demonstrates the point!)");
Struct keys/values not sorted by default:
array | |
---|---|
1 | ONE |
2 | TWO |
3 | THREE |
4 | FOUR |
5 | SIX |
6 | FIVE |
structSort() returns an array of keys according to the ordering of the key values:
SIX: ono
FIVE: rima
TWO: rua
ONE: tahi
THREE: toru
FOUR: wha
(It's a bit odd to sort numbers alphabetically by their name, but it demonstrates the point!)
XML
Within an XML document, the sequence of nodes within a given parent node have a sense of order. Whilst this node collection is not a native ColdFusion array (see below), it can be accessed via array notation, and some array functions can be used to manipulate the nodes. This will be covered in more detail when I examine ColdFusion's support for XML, but here's a demonstration of working with an XML node set as if it's an array:<cfxml variable="x">
<aaa>
<bbb>ccc</bbb>
<ddd eee="fff">ggg</ddd>
<hhh />
</aaa>
</cfxml>
<cfset children = x.aaa.xmlChildren>
<cfoutput>
Data type of xmlChildren: #children.getClass().getName()#<
But can access them as an array:<
<cfdump var="#children[2]#" label="Second child"><
And use array functions:<
Number of children: #arrayLen(children)#<
Delete the second child using arrayDeleteAt():<
<cfset arrayDeleteAt(children, 2)><!--- ddd --->
<cfdump var="#x#" label="<ddd> is gone">
</cfoutput>
Data type of xmlChildren: coldfusion.xml.XmlNodeArray
But can access them as an array:
Second child - xml element | |||||
---|---|---|---|---|---|
XmlName | ddd | ||||
XmlNsPrefix | |||||
XmlNsURI | |||||
XmlText | ggg | ||||
XmlComment | |||||
XmlAttributes |
| ||||
XmlChildren |
And use array functions:
Number of children: 3
Delete the second child using arrayDeleteAt():
<ddd> is gone - xml document [short version] | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
aaa |
|
xmlSearch()
Beyond accessing node sets via array notation, xmlSearch() preserves the ordering - as they appear in the XML doc relative to each other - via an array:
<cfxml variable="x">
<aaa>
<bbb>ccc</bbb>
<ddd eee="fff">ggg</ddd>
<hhh />
<iii>
<hhh jjj="kkk">lll</hhh>
</iii>
</aaa>
</cfxml>
Initial state of XML:<
<cfdump var="#x#">
<hr />
<hhh> nodes:<
<cfset hhh = xmlSearch(x, "//hhh")>
<cfdump var="#hhh#">
<hr />
Adjust one of the nodes via the array, not the XML doc<
<cfset hhh[1].xmlText = "mmm">
Note that the previously empty <hhh/> node now has xmlText:<
<cfdump var="#x#">
Initial state of XML:
xml document [short version] | |||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
aaa |
|
<hhh> nodes:
array | |||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 |
| ||||||||||||||||||||
2 |
|
Adjust one of the nodes via the array, not the XML doc
Note that the previously empty <hhh/> node now has xmlText:
xml document [short version] | |||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
aaa |
|
One thing to recall here (as demonstrated above) is that this is an array of references to the given nodes, not simply their values, so one can modify the XML directly via the start elements.
ColdFusion ORM
Many of the ORM function suite return arrays of objects. This topic is too big to cover under a paragraph in an article about arrays, and I'll write dedicated articles on the topic of ORM at some stage in the future.Misc
There s couple of odds 'n' sods that I can't think of a grouping for, so I'll just bung them down here.directoryList()
Rather than being a direct analogue of <cfdirectory action="list"> (which is what would have made sense), directoryList()'s default behaviour is to return an array of file paths.files = directoryList(expandPath("./"));
writeDump(var=files, top=5);
array | |
---|---|
1 | C:\ColdFusion10\cfusion\wwwroot\shared\git\blogExamples\arrays\arrayAppend.cfm |
2 | C:\ColdFusion10\cfusion\wwwroot\shared\git\blogExamples\arrays\arrayClear.cfm |
3 | C:\ColdFusion10\cfusion\wwwroot\shared\git\blogExamples\arrays\arrayContains.cfm |
4 | C:\ColdFusion10\cfusion\wwwroot\shared\git\blogExamples\arrays\arrayDelete.cfm |
5 | C:\ColdFusion10\cfusion\wwwroot\shared\git\blogExamples\arrays\arrayDeleteAt.cfm |
I think it should've just been implemented the same as <cfdirectory>, but however.
query [Filtered - Top 5 of 47 rows] | |||||||
---|---|---|---|---|---|---|---|
ATTRIBUTES | DATELASTMODIFIED | DIRECTORY | MODE | NAME | SIZE | TYPE | |
1 | [empty string] | 16/08/12 21:19 | C:\ColdFusion10\cfusion\wwwroot\shared\git\blogExamples\arrays | [empty string] | arrayAppend.cfm | 387 | File |
2 | [empty string] | 16/08/12 22:37 | C:\ColdFusion10\cfusion\wwwroot\shared\git\blogExamples\arrays | [empty string] | arrayClear.cfm | 108 | File |
3 | [empty string] | 22/08/12 00:32 | C:\ColdFusion10\cfusion\wwwroot\shared\git\blogExamples\arrays | [empty string] | arrayContains.cfm | 3244 | File |
4 | [empty string] | 23/08/12 22:27 | C:\ColdFusion10\cfusion\wwwroot\shared\git\blogExamples\arrays | [empty string] | arrayDelete.cfm | 4340 | File |
5 | [empty string] | 16/08/12 22:35 | C:\ColdFusion10\cfusion\wwwroot\shared\git\blogExamples\arrays | [empty string] | arrayDeleteAt.cfm | 341 | File |
(and, yes, I know one can still get a query from directoryList(), but I don't see why they created a function-version of <cfdirectory> so as to expose the functionality of it ito CFScript, and then implement it to behave differently. Daft. And, as I am becoming increasingly wont to say: remoinds me of PHP (equivalent things behaving differently is just messy and incoherent)).
CallStackGet()
Despite being named by Yoda, this is a handy new function in CF10. It dumps where the current line of code is in the sequence of files being processed, plus all the points in its ancestors where processing changed location (via an include, or a function call, etc). And it returns all this in an array:<!--callStackGet.cfm --->
<cfinclude template="./incCallStackGet.cfm">
<cfdump var="#getCallStack()#">
<!--- incCallStackGet.cfm --->
<cfscript>
function getCallStack(){ // I'm not Yoda
return callStackGet();
}
</cfscript>
array | |||||||||
---|---|---|---|---|---|---|---|---|---|
1 |
| ||||||||
2 |
|
Note: this is supported in Railo, but not in OpenBD.
Query columns
Query columns can be treated as arrays! This is quite handy. One needs to use associative array notation, eg: query["columnName"], and if one uses it in a place in which a string could be used, it'll simply act like the first element of the column, not the whole column. But if one uses it in a situation that calls for an array, CF will cast it to an array. EG:data = queryNew(
"id,english,maori",
"integer,varchar,varchar",
[
[1, "one", "tahi"],
[2, "two", "rua"],
[3, "three", "toru"],
[4, "four", "wha"]
]
);
writeOutput("Data type of a query column: " & data["english"].getClass().getName() & "<
");
writeDump(data);
writeOutput("isArray(): " & isArray(data["english"]) & "
");
writeOutput('Because writeDump() can take a string, CF will interpret data["english"] as a string in this situation: ');
writeDump(data["english"]);
writeOutput("<hr />");
writeOutput("Use arrayEach() to iterate over a query column:<
");
arrayEach(
data["english"],
function(element){
writeOutput(element & "<
");
}
);
Data type of a query column: coldfusion.sql.QueryColumn
query | |||
---|---|---|---|
ENGLISH | ID | MAORI | |
1 | one | 1 | tahi |
2 | two | 2 | rua |
3 | three | 3 | toru |
4 | four | 4 | wha |
Because writeDump() can take a string, CF will interpret data["english"] as a string in this situation: one
Use arrayEach() to iterate over a query column:
one
two
three
four
This doesn't work on Railo as it doesn't consider a column to be castable to an array. And this example code wouldn't run at all on OpenBD, so I dunno what it does. OpenBD supports neither the inline-data syntax of queryNew(), nor arrayEach(). I couldn't be bothered rewriting it to find out what OpenBD does.
On a whim I thought I'd have a look at whether I could concatenate an array onto a query column:
query = queryNew(
"id,english,maori",
"integer,varchar,varchar",
[
[1, "one", "tahi"],
[2, "two", "rua"],
[3, "three", "toru"],
[4, "four", "wha"]
]
);
array = ["rima","ono","whitu","waru","iwa","tekau"];
arrayAppend(query["maori"], array, true);
writeDump(variables);
Err.... no. ColdFusion gave me this rather odd-sounding error message:
The following information is meant for the website developer for debugging purposes. | |||
Error Occurred While Processing Request | |||
|
Nice. The gist being: "that won't work". I didn't expect it to.
Arguments scope
The Arguments scope is a bit of a curious one. It behaves rather like a struct, but it also behaves rather like an array. Whilst being neither, and not really know what it itself is. Here's what I mean:void function f(){
writeDump(arguments);
writeOutput("isArray(): #isArray(arguments)#<
");
writeOutput("isStruct(): #isStruct(arguments)#<
");
writeOutput("arguments.getClass().getName: #arguments.getClass().getName()#<
");
writeOutput("arrayLen(): #arrayLen(arguments)#<
");
writeOutput("structCount(): #structCount(arguments)#<
");
}
f("tahi", "rua", "toru", "wha");
writeOutput("<hr />");
f(one="tahi", two="rua", three="toru", four="wha");
writeOutput("<hr />");
struct | |
---|---|
1 | tahi |
2 | rua |
3 | toru |
4 | wha |
isStruct(): YES
arguments.getClass().getName: coldfusion.runtime.ArgumentCollection
arrayLen(): 4
structCount(): 4
struct | |
---|---|
four | wha |
one | tahi |
three | toru |
two | rua |
isStruct(): YES
arguments.getClass().getName: coldfusion.runtime.ArgumentCollection
arrayLen(): 4
structCount(): 4
So, which is it, ColdFusion? Is it not an array, or is it an array with length of 4? Railo is not so flummoxed, btw. It sees the thing as both an array and a struct. OpenBD sticks with CF on this one. I think Railo has it right here. One might argue that isArray() is telling the truth because the arguments scope is not exactly a coldfusion.runtime.Array. But then again neither is a query column (coldfusion.sql.QueryColumn), nor ar xmlChildren (coldfusion.xml.XmlNodeArray, and indeed nor is a java.util.Vector (although granted a CF array does extend Vector), nor is a java.util.ArrayList. Yet all of those return true from isArray(). So I reckon that's a bug (and have raised it accordingly: 3321638).
Looping
The last topic I want to cover is looping over arrays, because there's a coupla constructs in place specifically for arrays.<cfloop>
<cfloop> provides array-specific syntax. Unfortunately Adobe messed it up a bit, because the syntax is as follows:
<cfloop
index = "index name"
array = "array"
...
</cfloop>
Well: no. What's put in that variable is not the index, it's the element at that index. In the context of arrays, index != element. Even if they're both kinda related to each other, you can't just pick and choose what you call them. It doesn't work like that. Well it does in CF, but it oughtn't. Oh well.Here's an example of this in action:
<cfset numbers = ["tahi", "rua", "toru", "wha"]>
<cfloop index="number" array="#numbers#">
<cfoutput>#number#<
</cfoutput>
</cfloop>
Output:
tahi
rua
toru
wha
So note the "index" variable isn't the index - eg: 1,2,3,4 - it's the element (value) at that index. This wouldn't be such a problem if Adobe hadn't messed this up in a second way. This loop doesn't actually expose the index (the actual index) to the code at all. There's no way of knowing in that loop which index you're at, other than maintaining your own variable (which is a bit jerry-built). Adobe could have fixed this if they had called the element variable "element", by just adding in "index" as well, eg:
<cfset numbers = ["tahi", "rua", "toru", "wha"]>
<cfloop index="index" element="number" array="#numbers#">
<cfoutput>#index#: #number#<
</cfoutput>
</cfloop>
1: tahi
2: rua
3: toru
4: wha
But they can't fix it now, because they're already using index to hold the element. I suppose - depending on how CFML is parsed under the hood - if the parser encounters a <cfloop> tag which has index / element / array then it could be processed differently to just index / array? That should work. Then they could deprecate the current implementation (I've suggested this: 3321646).
One thing to note with using <cfloop> is that once the array as "passed into it", the array being looped-over cannot be changed. EG:
<cfloop index="number" array="#numbers#">
<cfoutput>#number#<br /></cfoutput>
<cfif number EQ "tahi">
<cfset arrayAppend(numbers, "rima")>
</cfif>
</cfloop>
Here we're trying to add a new element to the array as we go (the example seems quite contrived, but I have needed to do this before), but it doesn't do what one might expect:
tahi
rua
toru
wha
array | |
---|---|
1 | tahi |
2 | rua |
3 | toru |
4 | wha |
5 | rima |
The array element does actually get appended to the original array, but the one we're looping over isn't the original one. When passing the array into <cfloop>, a copy is made of it, and the code loops over that. One cannot get around this with a normal indexed loop either, for much the same reason:
<cfset numbers = ["tahi", "rua", "toru", "wha"]>
<cfloop index="i" from="1" to="#arrayLen(numbers)#">
<cfset number = numbers[i]>
<cfoutput>#number#<br /></cfoutput>
<cfif number EQ "tahi">
<cfset arrayAppend(numbers, "rima")>
</cfif>
</cfloop>
<cfdump var="#numbers#">
The TO value is calculated once before the loop starts. So it's 4 when the loop starts, and it doesn't matter what happens to the array, 4 it stays (this is more obvious than the previous example, granted).
One can get around this <cfloop> limitation with an equivalent for() loop:
numbers = ["tahi", "rua", "toru", "wha"];
for (i=1; i <= arrayLen(numbers); i++){
number = numbers[i];
writeOutput(number & "<br />");
if (number == "tahi"){
arrayAppend(numbers, "rima");
}
}
writeDump(numbers);
tahi
rua
toru
wha
rima
array | |
---|---|
1 | tahi |
2 | rua |
3 | toru |
4 | wha |
5 | rima |
This is because that sort of loop reevaluates the condition every iteration. So one might think the CFscript equivalant of <cfloop> over an array might work likewise. Here's an baseline example:
numbers = ["tahi", "rua", "toru", "wha"];
for (number in numbers){
writeOutput(number & "<br />");
}
writeDump(numbers);
(you get the idea with the output, so I'll spare you).
However I tried this:
numbers = ["tahi", "rua", "toru", "wha"];
for (number in numbers){
writeOutput(number & "<br />");
if (number == "tahi"){
arrayAppend(numbers, "rima");
}
}
writeDump(numbers);
And that resulted in this:
tahi
The web site you are accessing has experienced an unexpected error.
Please contact the website administrator.
The following information is meant for the website developer for debugging purposes. | ||||||||
Error Occurred While Processing Request | ||||||||
|
However if I comment out the arrayAppend() line, it all starts working again. So there's a bit of a bug there, I think (3321655). Even if ColdFusion doesn't support appending to the array when it's being looped over, that's what the error should say. Not just [nothing], and identifying the wrong line.
Note that Railo gets it right here, and OpenBD doesn't error, but it doesn't include the new element in the loop either. Still: at least it doesn't error.
Err...
... that's it.
At the moment I cannot think of anything else to investigate when it comes to arrays in ColdFusion. I gotta say it's all been a bit long-winded, but I've learned a fair bit, and I got to check out Railo and OpenBD a bit more whilst researching all this. All platforms had their idiosyncrasies (be they bugs or non-conformities or just brain farts), but that's to be expected, and I think I've mentioned this to all the relevant parties on every occasion, so we can see what happens there.
If I've forgotten to investigate / document something, do let me know. Do you know what though? I've had a complete gutsful of arrays for the time being, so I will follow-up anything I've missed, but perhaps not immediately.
Hopefully for every dozen people I bored rigid with all this stuff, there's 1-2 who actually got something out of it.
Righto.
--
Adam