Troubleshooting Tales: The Case of the Mysterious Array.sortOn() Call
Not long ago, I wrote an article for the Adobe Dev Center on handling cue points for external audio files. The article shows how to extend the native Sound class in ActionScript 2.0 and 3.0 to add support for a custom cuePoint event. In my SoundSynch.addCuePoint() method, I used Array.sortOn() to assure the growing list of cue points stays in chronological order. Here’s a quick look at the AS2 version:
// Add Cue Point
public function addCuePoint(cuePointName:String,
cuePointTime:Number):Void {
_cuePoints.push(
{
type: "cuePoint",
name: cuePointName,
time: cuePointTime,
target: this
}
);
_cuePoints.sortOn("time", Array.NUMERIC);
}
The Array.sortOn() method accepts two parameters: first, the name of the property on which to sort; second, and this is optional, the manner in which to sort. In this case, I wanted to sort numerically, so I specified Array.NUMERIC. Now, it gets fun.
I’m currently working on a project where I need to handle cue points for loaded MP3 files, so I snagged a copy of my own SoundSync class, modified it, and put it into practice. But then a mystery reared its head. My cuePoint events were firing out of order!
Right off the bat, I was seized with a panic that I’d put erroneous code into the Adobe article. I would hate to lead anyone astray — programming is hard enough without flawed code samples! — but I ran a couple quick isolated tests and determined that the class is fine as shown. That was a relief, but I still had my current issue.
In this project, I’m pulling data from an XML file, including which audio files to load and their cue points, so I checked the XML document again. Everything seemed all right there. What was the deal?
That’s the headache of debugging, isn’t it? It’s like running through a carnival House of Mirrors and smashing hard against the glass — it’s both disorienting and shocking, because everything looks just fine, and yet…. But actually, that’s not accurate. It’s not debugging that’s the headache. Debugging, when handled calmly, is fun. I don’t think debugging is discussed nearly as often as it should be in the forums, and that’s a shame, because at least one other person out there — Robert L. Reid, in How to Be a Programmer — thinks debugging is the first step to success (thanks to my friend Amy Niebel for the link!).
I had already done a valuable thing. I had tested the Array.sortOn() method in isolation — in a brand new FLA, away from the clutter of my existing code — and determined that my use of it was, in theory, sound. That gave me my bearings. Next, I fired up the Debugger panel (see “Debugging ActionScript” for a few tips) and found my SoundSync instance so I could look at its _cuePoints array. I opened that node and found the time property of a particular cuePoint object. Aha! Numerals were there, but they were wrapped in quotation marks. So this value was a string, not a number. How did this happen? Because my values were coming from an XML file, plain and simple. That’s just what happens when you load data from text files.
I pressed the F1 key and searched “Array class” in the ActionScript 2.0 Language Reference. I glanced through the Array.sortOn() method entry, but nothing jumped out at me. Then I clicked over to the Array.NUMERIC entry. Bingo.
Note that this constant only applies to numbers in the array; it does not apply to strings that contain numeric data (such as ["23", "5"]).
And my “numbers” were strings, so the numeric sort wasn’t being properly applied. Whew! I updated the class that loads the XML with the Number() function, which converts strings into numbers. Kind of lucky, actually, because I had just written a blog entry on a related topic last week, so it was fresh on my mind.
This was one of many bugs I ran into today, and I was thankful it took only a few minutes to fix. Debugging can take hours (or days, or longer!), but good troubleshooting often speeds the process considerably. Today’s example wasn’t an especially tough circumstance, but it could have taken a much longer time if I hadn’t used the Debugger panel. I’m hoping it’s helpful to people to see the occasional “case study” in action, because sometimes real-life examples are the best ones.
January 20th, 2007 at 12:56 pm
Immediately after reading that the “numbers” were sorting incorrectly, I thought, “Oh, he must be using Strings!”
Though I am a bit curious… wouldn’t your addCuePoint throw a mismatch error as your cuePointTime is strictly data typed as a Number?
Also, I’m thinking sorting after adding adding each element isn’t very efficient, because every time you add an element, you are resorting the ENTIRE list. So, I would perhaps either have a method that
1. adds all the cue points, and then sorts.
2. adds each cue point in the correct position by adding it before the first element that is greater than or equal to it (finding this element by looping through the array).
As a case study, I have timed the amount of time it takes for these 3 methods to sort (the same) list of 1000 elements, where each element is randomly chosen in the range 0-49.
I find that Method 1 (sorting afterwards) is faster in all cases.
Method 2 (manually finding where element belongs) is USUALLY faster than the original method, for arrays of length greater than 200, but slower for less than that. However, this algorithm could probably be made more efficient.
And for one very exaggerated test of having 1000 cue points, the original method took 14509 ms (!), method 1 took 13 ms, and method 2 took 6324 ms.
Here’s the code that I used:
//Random array
var r = [];
for(var i=0;i<1000;i++){
r[i] = random(50);
}
//For each element, add and sort
var s = getTimer();
var a = [];
for(var i=0;i<r.length;i++){
a[i] = r[i];
a.sort(Array.NUMERIC);
}
trace(getTimer()-s);
//Add all elements, then sort
var s = getTimer();
var b = [];
for(var i=0;i<r.length;i++){
b[i] = r[i];
}
b.sort(Array.NUMERIC);
trace(getTimer()-s);
//For each element, add in correct position
var s = getTimer();
var c = [];
for(var i=0;i<r.length;i++){
var addE = r[i];
for(var j=0;j<c.length;j++){
if(c[j]>=addE){
break;
}
}
c.splice(j,0,addE);
}
trace(getTimer()-s);
January 20th, 2007 at 6:55 pm
NSurveyor,
Now that you mention it, yes, it sure should — but it doesn’t. Isn’t that odd? I imagine it would in AS3, in which strong typing truly is strongly typed.
Heh, yes, that’s definitely true. In this case, I decided — perhaps poorly — to sweep that point under the rug in deference to the main theme, especially as this was a migration piece. Sometimes details can distract the from an article’s flow. Finding that balance can be a challenge.
Your point is well taken, as always, and your code samples are great!
Thanks, bro!