Friday 25 January 2013

Survey results: turning a string into an array of characters

G'day:
One of my readers pointed out to me if that if I tarry too long before reporting back on my recent survey about turning a string into an array of characters, then people would lose what little interest they have in the topic. And I admit it's not the world's most interesting topic anyhow. Anyway, I've got 35 responses, so I'll summarise the results.


Firstly, the reason I ran the survey was down to a thread on the Railo mailing list that I started when I noticed that my personal approach to this quandary didn't work in Railo (it's since been fixed, btw: the Railo team continue to rock). Some people's reaction there was to suggest they didn't think it was a valid thing to do. Now I don't put too much stock in their comments as the individuals concerned are not very discriminatory before leaping to Railo's defence (if Railo were to accidentally release  a build that was broken, these people would happily claim "well it's supposed to be broken". They have no credibility when it comes to assessing issues Railo might have, as they lack a sensible amount of objectivity in these matters), but it did make me think "really? Hmmm... that's interesting. Maybe there's something to this".

My approach to the task at hand would be to do this:

nz = "Aotearoa";
a = listToArray(nz, "");
writeDump(a);

I'm slightly with the bods that said suggested that's a bit hokey, because the notion of a zero-length delimiter is a bit of an odd one, but hey: it works, and there definitely is "no character" between each of the other characters. And I guess I have always taken this approach, so it'd never occurred to me to look for another one. That said: it's not something I do every day.

So, anyway, I wanted to gauge how other people would handle this task. I was not after flashy "clever" answers, I was just after "if you had to do this in your code, how would you?" I don't think I was clear with that, because I spoke to one of my CF pals and he didn't initially submit his answer because he figured "well my solution isn't an interesting one, so I didn't bother". But that was precisely the sort of solution I was after. The one that comes from initial gut-feel. I then guilt-tripped / brow-beat him into posting an answer.

Right. How did other people answer? I have not looked at the results myself yet (save the first few), so this will be as much a surprise to me as it is to you.

listToArray()

15 people (incl. myself) took the approach I mentioned above. This was just shy of half the respondents.

toCharArray()

Seven people took this approach:

nz = "Aotearoa";
a = nz.toCharArray();
writeDump(a);

This is leveraging the fact that CFML strings are just java.lang.String objects, so can use any methods from that Java class. This is always good to remember if CFML doesn't have a string function you need.

Unfortunately, this is not a great approach... it looks like it results in a CF array, but it actually returns a Java array.  Check this code out:

nz = "Aotearoa";
a = nz.toCharArray();
writeDump(a);

writeOutput("Returned from toCharArray(): #a.getClass().getName()#<br />");
writeOutput("A CF 'array': #arrayNew(1).getClass().getName()#<br />");

try {
    arrayDeleteAt(a, 1);
}
catch (any e){
    writeDump(e);
}

This outputs:

array
1A
2o
3t
4e
5a
6r
7o
8a
Returned from toCharArray(): [C
A CF 'array': coldfusion.runtime.Array
struct
Message[empty string]
StackTracejava.lang.UnsupportedOperationException at java.util.AbstractList.remove(AbstractList.java:144) at coldfusion.runtime.Array.ArrayDeleteAt(Array.java:217) at [etc]
TagContext
Typejava.lang.UnsupportedOperationException

The problem here is that Java arrays are immutable (one cannae change 'em). This might not be a problem when doing this, but it's something to bear in mind.

One of the people answering - Peter Pham - alluded to this issue in his answer.

Using a loop to append each char to an array

Five people suggested this kind of approach:

nz = "Aotearoa";
a = [];
for (i=1; i <= len(nz); i++){
    arrayAppend(a, mid(nz, i, 1));
}
writeDump(a);

That's fine, and works well.

One person used a conditional loop, thus:

nz = "Aotearoa";
arr = [];
while (len(nz)){
    arrayappend(arr, left(nz,1));
    nz = removechars(nz, 1, 1);
}
writeDump(arr);

Fair enough. A bit more verbose than the character-by-character loop, but definitely something to bear in mind.

And another wordier-still take on the conditional loop:

<cfset nz = "Aotearoa">
<cfset arr = ArrayNew(1)>
<cfloop condition="len(nz)">
    <cfset ArrayAppend(arr, Left(nz, 1))>
    <cfif len(nz) gt 1>
        <cfset nz = Right(nz, len(nz)-1)>
    <cfelse>
        <cfset nz = "">
    </cfif>
</cfloop>
<cfdump var="#arr#">

Intriguing, but that's a lot more code than is really necessary ;-)

As a function

Here's something interesting. Only one person thought to wrap this up in a function:

<cfscript>
    function stringToArray(string) {
        var result = [];
        var i = 1;
        var chrarray = createObject("java","java.lang.String").init(string).toCharArray();
        for (i=1;i<=arrayLen(chrarray);i++)
            arrayAppend(result,JavaCast("String",chrarray[i].toString()));
        return result;
    }
</cfscript>
<cfdump var="#stringToArray('Aotearoa')#" />

This combines both the loop Java char array and the loop approach to make an actual CFML array. Good and thorough. And, of course, a good idea to wrap this sort of thing into a function.

reMatch()

Two people suggested this:

<cfset nz = "Aotearoa">
<cfset chars = rematch(".", nz)>
<cfdump var="#chars#">

This is the tidiest, most succinct and purest CFML answer, so gets the prize. Jason Dean came up with it first, but he didn't want the beer so I have disqualified him (people who don't want beer will quickly be disqualified by me in any given situation ;-). So the other person who came up with the same answer was Julian Halliwell. And - Julian unless you don't want the beer either - he wins the prize. I'm sure we'll cross paths at some point so that you can claim it! Julian mentioned that he got the idea from Leigh on StackOverflow, who answered a very similar question to the one I posed here!

WTF?

Last but not least, there was also this answer:

<cfsavecontent variable="nz ">
    aa=["A",RemoveChars(left( dateFormat(.5+SpanexCluding (HAsh(evaluate("256+128+16+ 4+2")),"'bAABaaC'")),4),1,3 ).toLowerCase(),LCase(!('') .matches((("'").valueOf(' ' )))).charAt(val(reverse(cgi .server_protocol))-day(2)), chr(len(RepeatstrinG('oort' ,16))+5).toLowerCase()];aa. append([aa [1].toLowerCase( ),replaceNoCase(" r ", ' ','',"all"),LISTGetAt(aa. toString(),2),lcase(chr(asc (ARRAYfirst(aa))+asc(' '))) ],1+False);
</cfsavecontent>
<cfset araA = variables['nz '].split('(?<!;);')>
<cfif t= eVALUatE(araA.1).toString( )!=len(Trim(''))>
    <cfscript>
        evaluate(araA[arrayLEN(aa)/ (1+1)]);
    </cfscript>
    <cfparam name="request.nz" default=" #serializeJSON(duplIcate([[ structcopy({1:aa})]])[1])#" >
<cfelse/>
</cfif>
<cfset nz= evaluate(request.nz)[1][1]>
<cfset dumpnz = dump(nz) />

I have no idea what is supposed to be going on there, but it's fantastic code (no, it doesn't work). I think it was designed to make my head explode.

It's a shame you didn't leave your name, as I'd've given you a beer just for the fact I got a good chuckle out of it.

Thanks to everyone who responded, and hey... if we cross paths and identify yourself, obviously I'll buy you a pint anyhow! Any excuse for a pint, really ;-)

--
Adam