G'day:
Just when you thought it was safe... here's some more on arrays in
ColdFusionRailo. I've already blathered on about the ins and outs of arrays in ColdFusion... well more like arrays in
CFML as I cover some stuff in not only ColdFusion but also Railo and a bit on OpenBD too:
But a comment from Gert against one of my blog posts was tucked away in the back of my mind... Railo does some of its own thing when it comes to arrays (and structs, lists, etc).
I'm only just looking at this stuff for the first time
right now as I type this blog article, so I am not yet prepared to say "I think this is something Adobe should add to ColdFusion", but I suspect this will be my conclusion by the end of the article.
First: a bit of background to why I wrote the above paragraph.
In
one of my surveys, I asked what syntax people prefer. More procedural syntax like this:
result = someFunction(myObject, args);
or or a more object-oriented approach:
result = myObject.someMethod(args);
CFML as found itself in a bit of an awkward place. Historically - up to and including CF5 - CFML was a completely procedural language. However with CFMX6.1 (let's forget CFMX6.0) it started a move into object orientation, and has moved more and more in that direction as subsequent versions come out. However this is only in the context of developer-written code: CFML has taken an odd route in that it provides the capability to write OO code, but CFML itself has stayed procedural. I think this has been a serious mistake, and we've been left with a very cluttered language that adds more and more "general" functions littering the place. There are no-fewer than 50 functions prefixed "image" (eg: imageResize()), 39 for spreadsheets, and even 20-odd for lists. This was the wrong approach. In CFMX6, when CFML started going OO, that should have extended both to code I wrote, as well as the code Adobe wrote. I think all of these functions should be deprecated, and reimplemented as methods upon an object. This would be a move to make the language more coherent.
As
Gert pointed out: Railo is already
making headway in that direction in Railo 4.x.
So for the sake of completeness, and also to get myself up to speed with all this stuff, I'm doing this "last" (hopefully!) article on arrays in CFML.
array()
This can be used to create a new array, much like the short-cut square-bracket notation can:
<cfprocessingdirective pageencoding="utf-8">
<cfscript>
daysOfWeek = array("Rahina", "Ratu", "Raapa", "Rapare", "Ramere", "Rahoroi", "Ratapu");
writeDump(daysOfWeek);
</cfscript>
And can be "nested" to create multi-dimensional arrays:
<cfprocessingdirective pageencoding="utf-8">
<cfscript>
numbers = array(1, array(2, "two"), array(3, array("three", "toru")));
writeDump(numbers);
</cfscript>
I've been looking at some PHP recently, and it has an array() construct that can either be used for creating indexed arrays (like CFML ones), or associative ones (like CFML structs). The difference being simply that if one doesn't specify a key for each element it's an indexed array; if one
does specify a key, then it's an associative array. This makes sense in PHP because to PHP both data structures are "arrays". I wondered what Railo would do here, and tried this:
<cfprocessingdirective pageencoding="utf-8">
<cfscript>
daysOfWeek = array(monday="Rahina", tuesday="Ratu", wednesday="Raapa", thursday="Rapare", friday="Ramere", saturday="Rahoroi", sunday="Ratapu");
writeDump(daysOfWeek);
</cfscript>
So... um... I think that's a bug. if it doesn't support the syntax, it should error. It should not
ignore half the code because it doesn't know what to do with it.
To be honest, I question the merits of array(), as [] is just cleaner. I suspect array() predates []. If so: it certainly would have been handy at the time.
append() / prepend()
These work just like arrayAppend() and arrayPrepend():
<cfprocessingdirective pageencoding="utf-8">
<cfscript>
daysOfWeek = array("Rapare"); // Thursday
daysOfWeek.prepend("Raapa"); // Wednesday
daysOfWeek.append("Ramere"); // Friday
daysOfWeek.prepend("Ratu"); // Tuesday
daysOfWeek.append("Rahoroi"); // Saturday
daysOfWeek.prepend("Rahina"); // Monday
daysOfWeek.append("Ratapu"); // Sunday
writeDump(daysOfWeek);
</cfscript>
insertAt()
This is the eqivalent of arrayInsertAt():
<cfprocessingdirective pageencoding="utf-8">
<cfscript>
daysOfWeek = array("Rahina", "Ratu", "Raapa", /*NO THURSDAY */ "Ramere", "Rahoroi", "Ratapu");
daysOfWeek.insertAt(4, "Rapare");
writeDump(daysOfWeek);
</cfscript>
(you'll be getting the idea of the dump by now, so I'll spare you)
set()
Equivalent of arraySet():
<cfprocessingdirective pageencoding="utf-8">
<cfscript>
daysOfWeek = array("Rahina");
daysOfWeek[7] = "Ratapu";
daysOfWeek.set(3, 5, "FILLER");
writeDump(daysOfWeek);
</cfscript>
resize (arrayResize())
This just sets the size of the array as per arrayResize():
<cfscript>
a = [];
writeOutput("Initial size: #a.len()#<br />");
a.resize(1000);
writeOutput("After resize: #a.len()#<br />");
</cfscript>
Output:
Initial size: 0
After resize: 1000
deleteAt() (arrayDeleteAt())
Here I've doubled-up on
Thursdays, so I need to get rid of one:
<cfprocessingdirective pageencoding="utf-8">
<cfscript>
daysOfWeek = array("Rahina", "Ratu", "Raapa", "Rapare", "Rapare", "Ramere", "Rahoroi", "Ratapu"); // Rapare is duplicated
daysOfWeek.deleteAt(4);
writeDump(daysOfWeek);
</cfscript>
(same old output as we'd expect)
clear() (arrayClear())
<cfprocessingdirective pageencoding="utf-8">
<cfscript>
daysOfWeek = array("Rahina", "Ratu", "Raapa", "Rapare", "Ramere", "Rahoroi", "Ratapu");
daysOfWeek.clear();
writeDump(daysOfWeek);
</cfscript>
This results in an empty array.
sort() (arraySort())
Here I'm also checking whether Railo has the same collation glitch with this method that it has with
arraySort() (and that CF10 works around in a very dumb way):
<cfprocessingdirective pageencoding="UTF-8">
<cfscript>
frenchLetters = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","é","à ","è","ù","â","ê","î","ô","û","ë","ï","ü","ÿ","ç"];
frenchLetters.sort("text", "asc");
writeDump(frenchLetters);
</cfscript>
I'll truncate the output somewhat:
Array |
1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
| [...] |
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
| [etc] |
Note that all the "extended" characters appear after Z, whereas they ought to be sorted inline with their non-accented counterparts. I
raised this with the Railo bods, but never got any feedback. I shall chase.
swap() (arraySwap())
In this example the first two elements are transposed, so I swap() them:
<cfprocessingdirective pageencoding="utf-8">
<cfscript>
daysOfWeek = array("Ratu", "Rahina", "Raapa", "Rapare", "Ramere", "Rahoroi", "Ratapu"); // Ratu and Rahina in the wrong order
daysOfWeek.swap(1, 2);
writeDump(daysOfWeek);
</cfscript>
isDefined() (arrayIsDefined())
Here I defined an array in which I only populate the second element, and then check the existence of the first three elements. This demonstrates the isDefined() method not only checks for non-defined elements within the bounds of the array, but also checks for elements outwith the bounds of the array.
<cfprocessingdirective pageencoding="utf-8">
<cfscript>
daysOfWeek = array();
daysOfWeek[2] = "Rahina";
for (i=1; i <= 3; i++){
try {
writeOutput("#i#: #daysOfWeek.isDefined(i)#<br />");
} catch (any e){
writeDump(e);
}
}
</cfscript>
This outputs:
1: false
2: true
3: false
isEmpty() (arrayIsEmpty())
<cfprocessingdirective pageencoding="utf-8">
<cfscript>
daysOfWeek = array("Rahina", "Ratu", "Raapa", "Rapare", "Ramere", "Rahoroi", "Ratapu");
writeOutput("daysOfWeek.isEmpty: #daysOfWeek.isEmpty()#<br />");
daysOfWeek.clear();
writeOutput("daysOfWeek.isEmpty() after daysOfWeek.clear(): #daysOfWeek.isEmpty()#<br />");
</cfscript>
Output:
daysOfWeek.isEmpty: false
daysOfWeek.isEmpty() after daysOfWeek.clear(): true
len() / min() / max() / sum() / avg()
These are all the same as their function-based equivalents:
numbers = [2,4,6,0,-5,-3,-1];
writeOutput("len(): #numbers.len()#<br />");
writeOutput("min(): #numbers.min()#<br />");
writeOutput("max(): #numbers.max()#<br />");
writeOutput("sum(): #numbers.sum()#<br />");
writeOutput("avg(): #numbers.avg()#<br />");
Output:
len(): 7
min(): -5
max(): 6
sum(): 3
avg(): 0.428571428571
toList() (arrayToList())
<cfprocessingdirective pageencoding="utf-8">
<cfscript>
daysOfWeek = array("Rahina", "Ratu", "Raapa", "Rapare", "Ramere", "Rahoroi", "Ratapu");
writeOutput(daysOfWeek.toList());
</cfscript>
Output:
Rahina,Ratu,Raapa,Rapare,Ramere,Rahoroi,Ratapu
is() (isArray())
No. Don't be silly.
(but yes, I did try it! ;-)
contains() / containsNoCase() / find() / findNoCase()
These all operate as their array-prefixed function equivalents work in Railo (which might be slightly differently from in ColdFusion. See a
separate article on that).
<cfprocessingdirective pageencoding="utf-8">
<cfset daysOfWeek = array("Rahina", "Ratu", "Raapa", "Rapare", "Ramere", "Rahoroi", "Ratapu")>
<cfoutput>
contains("Rahina"): #daysOfWeek.contains("Rahina")#<br />
contains("RAHINA"): #daysOfWeek.contains("RAHINA")#<br />
<hr />
containsNoCase("Ratu"): #daysOfWeek.containsNoCase("Ratu")#<br />
containsNoCase("RATU"): #daysOfWeek.containsNoCase("RATU")#<br />
<hr />
find("Raapa"): #daysOfWeek.find("Raapa")#<br />
find("RAAPA"): #daysOfWeek.find("RAAPA")#<br />
<hr />
findNoCase("Rapare"): #daysOfWeek.findNoCase("Rapare")#<br />
findNoCase("RAPARE"): #daysOfWeek.findNoCase("RAPARE")#<br />
<hr />
</cfoutput>
Outputs:
contains("Rahina"): 1
contains("RAHINA"): 0
containsNoCase("Ratu"): 2
containsNoCase("RATU"): 2
find("Raapa"): 3
find("RAAPA"): 0
findNoCase("Rapare"): 4
findNoCase("RAPARE"): 4
findAll() / findAllNoCase()
These are a rare misfire from Railo, I think. They work by taking a callback function as the argument, and depending on whether the callback returns true or false, adds the match to an array that is returned. Why do I say this is a misfire? Well here's some example code:
<cfprocessingdirective pageencoding="utf-8">
<cfscript>
daysOfWeek = array("Rahina", "Ratu", "Raapa", "Rapare", "Ramere", "Rahoroi", "Ratapu");
daysOfWeek.append("RAMERE"); // append another "similar" entry, but we expect not to match it due to doing a case-senstive match
matches = daysOfWeek.findAll(
// so should be case-sensitive, given the method name...
function(element){
return !compare(element, "Ramere"); // ...but it's up to our callback to determine whether it's case-sensitive!
}
);
writeDump(var=matches, label='daysOfWeek.findAll("Ramere")');
daysOfWeek.append("RAHOROI"); // append another "similar" entry. We DO expect to match it due to doing a case-insenstive match
matches = daysOfWeek.findAllNoCase(
function(element){
return element == "RAHOROI"; // shoud be case-insensitive
}
);
writeDump(var=matches, label='daysOfWeek.findAllNoCase("RAHOROI")');
writeDump(daysOfWeek);
</cfscript>
And the output:
daysOfWeek.findAll("Ramere") |
|
daysOfWeek.findAllNoCase("RAHOROI") |
|
Firstly: what's the point of having both findAll() and findAllNoCase(), if it's up to the callback code to do the comparison? Note that the findAll() (which should be case-sensitive) needs to use compare() to do a case-sensitive comparison. Whilst this is correct, it demonstrates that if I just did an equality evaluation there, then the result from findAll() would not actually be case-sensitive. So there's no point in having two functions here: the differentiation between case-sensitivity in the function names is meaningless (and misleading).
Secondly: it doesn't seem like findAllNoCase() even works. No matter what I do, I just get an empty array back.
Thirdly: aren't these doing exactly the same thing as filter() (see below)?
That said, as I can find no docs for these (or the function versions) I do not know if I am using them correctly or not. I've made a reasonable educated guess though, I think?
filter() (arrayFilter())
This does what findAll() and findAllNoCase() don't quite implement sensibly:
<cfprocessingdirective pageencoding="utf-8">
<cfscript>
daysOfWeek = array("Rahina", "Ratu", "Raapa", "Rapare", "Ramere", "Rahoroi", "Ratapu");
matches = daysOfWeek.filter(
function(element){
return reFindNoCase("^Rat", element);
}
);
writeDump(var=matches);
</cfscript>
This makes more sense than its "findAll" counterparts: it just does a filter (whatever that filter might be, in this case: days that start with "rat") on each element, and either includes or discards them from the returned array.
delete() (arrayDelete())
As mentioned in the other article about array functions, in which I cover arrayDelete(), this function deletes elements of an array by value, rather than by index. The utility of this functionality is questionable, I think. Anyway, here it is:
<cfprocessingdirective pageencoding="utf-8">
<cfscript>
daysOfWeek = array("Rahina", "DELETEME", "Ratu", "DELETEME", "Raapa", "DELETEME", "Rapare", "DELETEME", "Ramere", "DELETEME", "Rahoroi", "DELETEME", "Ratapu", "DELETEME");
daysOfWeek.delete("DELETEME", "ALL");
writeDump(daysOfWeek);
</cfscript>
Note I specified "ALL". Had I not, just the first match would have been deleted. This is something that ColdFusion doesn't have that I lamented the absence of. Railo does indeed support this. Well done.
each() (arrayEach())
This is similar to filter(), except it just transforms the element value. It does not act on the array itself, just the element. This reduces its utility in my view.
<cfprocessingdirective pageencoding="utf-8">
<cfscript>
daysOfWeek = array("Rahina", "Ratu", "Raapa", "Rapare", "Ramere", "Rahoroi", "Ratapu");
daysOfWeek.each(
function(element){
param name="i" default=1;
writeOutput("#i++# #uCase(element)#<br />");
}
);
</cfscript>
So this upper-cases the element value, outputting it and its index (which I have to calculate by hand, rather than Railo passing it to me. Grumble).
1 RAHINA
2 RATU
3 RAAPA
4 RAPARE
5 RAMERE
6 RAHOROI
7 RATAPU
slice() (arraySlice())
This chops out a chunk of an array and returns it:
<cfprocessingdirective pageencoding="utf-8">
<cfscript>
daysOfWeek = array("Rahina", "Ratu", "Raapa", "Rapare", "Ramere", "Rahoroi", "Ratapu");
writeDump(var=daysOfWeek, label="Original data");
new = daysOfWeek.slice(2, 3);
writeDump(var=new, label="2,3,4 (Ratu-Rapare)");
new = daysOfWeek.slice(4);
writeDump(var=new, label="4- (Rapare-Ratapu)");
new = daysOfWeek.slice(-5, 3);
writeDump(var=new, label="2,3,4 (Ratu-Ramere)");
</cfscript>
Others
Railo also has a few array functions that ColdFusion doesn't have:
reverse() (arrayReverse())
This does what it says on the tin:
<cfprocessingdirective pageencoding="utf-8">
<cfscript>
daysOfWeek = array("Rahina", "Ratu", "Raapa", "Rapare", "Ramere", "Rahoroi", "Ratapu");
reversed = daysOfWeek.reverse();
writeDump(var=reversed);
</cfscript>
I fell into a trap here. I presumed this function works like other array functions tend to, and reverses the array inline, rather than returning a different array. IE, this is the code I initially tried:
<cfprocessingdirective pageencoding="utf-8">
<cfscript>
daysOfWeek = array("Rahina", "Ratu", "Raapa", "Rapare", "Ramere", "Rahoroi", "Ratapu");
daysOfWeek.reverse();
writeDump(var=daysOfWeek);
</cfscript>
Given the way pretty much all other array functions work, I think this discrepancy might count as a bug.
merge (arrayMerge())
This seems to do what CF10 does with arrayAppend(), by adding the MERGE argument:
<cfprocessingdirective pageencoding="utf-8">
<cfscript>
weekdays = array("Rahina", "Ratu", "Raapa", "Rapare", "Ramere");
weekend = array("Rahoroi", "Ratapu");
daysOfWeek = weekdays.merge(weekend);
writeDump(var=daysOfWeek);
</cfscript>
(This outputs the whole week, in case it needed clarification ;-)
first() / last() / mid() (arrayFirst() / arrayLast() / arrayMid())
These do what they sound like.
<cfprocessingdirective pageencoding="utf-8">
<cfscript>
daysOfWeek = array("Rahina", "Ratu", "Raapa", "Rapare", "Ramere", "Rahoroi", "Ratapu");
results.first = daysOfWeek.first();
results.last = daysOfWeek.last();
results.mid = daysOfWeek.mid(2,5);
writeDump(results);
</cfscript>
last() I can understand. It saves one having to do this: myArray[myArray.len()]. But first()? Seriously? Going myArray.first() is preferable to myArray[1]? No, it's not. So that's a waste of time. Equally mid() is a less-functional version of slice(). Why does Railo also need mid()? I suspect the answer is "for completeness". I s'pose.
indexExists() (arrayIndexExists())
Seems to be the same as isDefined(). Waste of time. Well one of them is. This one probably has a more sensible name. Perhaps it - or arrayIndexExists() - predates arrayIsDefined(), the latter only being implemented for ColdFusion compat?
toStruct() (arrayToStruct())
<cfprocessingdirective pageencoding="utf-8">
<cfscript>
daysOfWeek = array("Rahina", "Ratu", "Raapa", "Rapare", "Ramere", "Rahoroi", "Ratapu");
result = daysOfWeek.toStruct();
writeDump(var=result);
</cfscript>
I seriously question the merits of this too, actually. It simply does this:
I
guess there are situations wherein some other method or something is expecting a struct and you have an array, so what're you to do? Use toStruct(), obviously. Hmmm. I think it's seldom going to be the case where the result of a toStruct() call is going to be useful for anything. I
presume there was something in mind when this function was written though.
I think that's it. I've gone through all the functions I listed in parts 2&3 of the original array coverage, as well as snooped around for Railo-only functions.
Obviously I've highlighted where I think there are some glitches here, but on the whole I think it's jolly good. Hopefully this'll get onto Adobe's radar, and it'll all find its way into CF11. I should be in a position to... err... "influence" this, and I'll try my hardest.
Good work, Railo dudes.
--
Adam