Arrays: Chapter 5 - ActionScript 3.0 Cookbook
Pages: 1, 2, 3, 4

## Section 5.9: Storing Complex or Multidimensional Data

### Problem

You have two or more sets of related data and you want to be able to keep track of the relationships between their elements.

### Solution

Use parallel arrays, an array of arrays (a multidimensional array), or an array of objects.

### Discussion

You can create two or more parallel arrays in which the elements with the same index in each array are related. For example, the `beginGradientFill()` method, discussed in Chapter 7, uses three parallel arrays for the colors, alphas, and ratios of the values used in the gradient. In each array, the elements with the same index correspond to one another.

To create parallel arrays, populate multiple arrays such that the elements with the same index correspond to one another. When you use parallel arrays, you can easily retrieve related data, since the indexes are the same across the arrays; for example:

```var colors:Array = ["maroon", "beige",    "blue",     "gray"];
var years:Array  = [1997,     2000,       1985,       1983];
var makes:Array  = ["Honda",  "Chrysler", "Mercedes", "Fiat"];

// Loop through the arrays. Since each array is the same
// length, you can use the length property of any of them
// in the for statement. Here, we use makes.length.
for (var i:int = 0; i < makes.length; i++) {
// Displays:
// A maroon 1997 Honda
// A beige 2000 Chrysler
// A blue 1985 Mercedes
// A gray 1983 Fiat

// Display the elements with corresponding indexes
// from the arrays.
trace("A " + colors[i] + " " +
years[i] + " " +
makes[i]);
}```

Be careful when manipulating parallel arrays. If you add or remove elements from the arrays, you have to be certain to add or remove related data at the same position in every array. Otherwise the arrays will be out of sync and useless.

Another option for working with multiple sets of data is to create a multidimensional array, which is an array of arrays (i.e., an array in which each element is another array):

```// Create an array, cars, and populate it with elements that
// are arrays. Each element array represents a car and
// contains three elements (color, year, and make).
var cars:Array = new Array();
cars.push(["maroon", 1997, "Honda"]);
cars.push(["beige", 2000, "Chrysler"]);
cars.push(["blue", 1985, "Mercedes"]);
cars.push(["gray", 1983, "Fiat"]);

// Loop through the elements of the cars array.
for (var i:int = 0; i < cars.length; i++) {
// The output is the same as in the
// earlier parallel arrays example:
// A maroon 1997 Honda
// A beige 2000 Chrysler
// A blue 1985 Mercedes
// A gray 1983 Fiat

// Output each element of each subarray, cars[i].
// Note the use of two successive indexes in brackets,
// such as cars[i][0].
trace("A " + cars[i][0] + " " +
cars[i][1] + " " +
cars[i][2]);
}```

The following is another way to view the two-dimensional `cars` arrays' contents. This displays the elements in a long list (the formatting isn't as nice as in the previous example, but it shows the array structure more clearly):

```// Loop through the elements of the cars array.
for (var i:int = 0; i < cars.length; i++) {
// Loop through the elements of each subarray, cars[i].
for (var j:int = 0; j < cars[i].length; j++) {
// Note the use of two successive indexes in brackets,
// cars[i][j].
trace("Element [" + i + "][" + j + "] contains: " +
cars[i][j]);
}
}```

In the preceding example (the array of arrays), it is hard to discern the meaning of something like `cars[i][0]` or `cars[i][j]`. Furthermore, if the order of elements in a subarray changes, you would have to modify the code (or it might erroneously display "A Honda maroon 1997" instead of "A maroon 1997 Honda").

One alternative is to work with related data using an array of objects (associative arrays). This technique is similar to working with an array of arrays, but it offers the advantage of named properties. When you use an array of arrays, you must reference each value by its numbered index. However, when you use an array of objects, you can reference the data by property name instead of its index number. You can specify the properties of the object in any order you like because you'll refer to them later by name, not by number:

```// Create an array, cars, and populate it with objects.
// Each object has a make property, a year property,
// and a color property.
var cars:Array = new Array();

// Here, object literals are used to define three properties
// for each car; the object literals are added to
// the main array.
cars.push({make: "Honda",    year: 1997, color: "maroon"});
cars.push({make: "Chrysler", year: 2000, color: "beige"});
cars.push({make: "Mercedes", year: 1985, color: "blue"});
cars.push({make: "Fiat",     year: 1983, color: "gray"});

// Loop through the cars array.
for (var i:int = 0; i < cars.length; i++) {
// The output is the same as in the earlier examples,
// but each value is referenced by its property name,
// which is more programmer-friendly.
trace("A " + cars[i].color + " " +
cars[i].year + " " +
cars[i].make);
}```

Recipe 5.15 covers associative arrays, in which elements are accessed by name instead of number.

## Section 5.10: Sorting or Reversing an Array

### Problem

You want to sort the elements of an array.

### Solution

Use the `sort( )` method. For arrays of objects, you can also use the `sortOn( )` method.

### Discussion

You can perform a simple sort on an array using the `sort( )` method. The `sort( )` method, without any parameters, sorts the elements of an array in ascending order. Elements are sorted according to the Unicode code points of the characters in the string (roughly alphabetical for Western European languages).

```var words:Array = ["tricycle", "relative", "aardvark", "jargon"];
words.sort( );
trace(words); // Displays: aardvark,jargon,relative,tricycle```

The `sort( )` method, by default, is very useful if you want to sort the elements of an array in ascending, alphabetical order. However, there are some caveats. Namely, the sort is case-sensitive, and it sorts numbers "alphabetically" instead of numerically. Fortunately, ActionScript allows you to pass one of several constants to the `sort( )` method in order to sort with different guidelines.

You sort an array in descending order using the `Array.DESCENDING` constant:

```var words:Array = ["tricycle", "relative", "aardvark", "jargon"];
words.sort(Array.DESCENDING);
trace(words); // Displays: tricycle,relative,jargon,aardvark```

As mentioned, the `sort( )` method runs a case-sensitive sort by default. It places elements starting with uppercase characters before elements starting with lowercase characters. The following illustrates the point:

```var words:Array = ["Tricycle", "relative", "aardvark", "jargon"];
words.sort( );
trace(words); // Displays: Tricycle,aardvark,jargon,relative```

You can use the `Array.CASEINSENSITIVE` constant to run a case-insensitive sort:

```var words:Array = ["Tricycle", "relative", "aardvark", "jargon"];
words.sort(Array.CASEINSENSITIVE);
trace(words); // Displays: aardvark,jargon,relative,Tricycle```

When you sort an array of numbers, the values are sorted according to the ASCII equivalents of the digits rather than in numerical order. The following code illustrates the point:

```var scores:Array = [10, 2, 14, 5, 8, 20, 19, 6];
scores.sort( );
trace(scores);   // Displays: 10,14,19,2,20,5,6,8```

You can use the `Array.NUMERIC` constant with the `sort( )` method to sort an array of numbers numerically:

```var scores:Array = [10, 2, 14, 5, 8, 20, 19, 6];
scores.sort(Array.NUMERIC);
trace(scores);   // Displays: 2,5,6,8,10,14,19,20```

There are two other possible constants you can use with the `sort( )` method: `Array.UNIQUESORT` and `Array.RETURNINDEXEDARRAY`. In some situations you want to sort the array only if it contains unique elements. In this case, use the `Array.UNIQUESORT` constant; Flash only sorts the array if the elements are unique. Otherwise, the `sort( )` method returns 0, and the array is not sorted:

```var ranking:Array = [2,5,6,3,1,1,4,8,7,10,9];
var sortedRanking:Object = ranking.sort(Array.UNIQUESORT);
trace(sortedRanking);   // Displays: 0
trace(ranking);  // Displays: 2,5,6,3,1,1,4,8,7,10,9```

Frequently, you may want to get the sorted order of an array's elements, but you don't want to change the original array because other parts of your application may depend on the existing order. For example, if you have parallel arrays, and you sort one array, its relationship with the other arrays is no longer valid. In such scenarios the `Array.RETURNINDEXEDARRAY` constant is very helpful. It allows you to return a new array containing the indices of the elements of the original array in sorted order, as illustrated in the following code:

```var words:Array = ["tricycle", "relative", "aardvark", "jargon"];
var indices:Array = words.sort(Array.RETURNINDEXEDARRAY);
trace(words);   // Displays: tricycle,relative,aardvark,jargon
trace(indices); // Displays: 2,3,1,0
for(var i:int = 0; i < words.length; i++) {
/* Displays:
aardvark
jargon
relative
tricycle
*/
trace(words[indices[i]]);
}```

You aren't limited to one sort modifier at a time. You can combine the combine the constants using the bitwise `OR` operator (`|`). The following code illustrates a case-insensitive, descending sort:

```var words:Array = ["Tricycle", "relative", "aardvark", "jargon"];
words.sort(Array.CASEINSENSITIVE | Array.DESCENDING);
trace(words);   // Displays: Tricycle,relative,jargon,aardvark```

Sometimes you want to reverse the order of the elements in an array. The `sort( )` method allows you to run ascending, descending, case-sensitive, case-insensitive, and numeric sorts, but it does not allow you to simply reverse the order of the elements. Instead, you can use the `reverse( )` method. The `reverse( )` method does just what its name suggests; it reverses the order of the elements:

```var words:Array = ["tricycle", "relative", "aardvark", "jargon"];
words.reverse( );
trace(words);   // Displays: jargon,aardvark,relative,tricycle```

The preceding portion of this recipe described how to sort arrays in which the elements are strings or numbers. You can also sort arrays of objects of any type using the `sortOn( )` method. The `sortOn( )` method requires a string parameter specifying the name of the property on which to sort the elements:

```var cars:Array = new Array();
cars.push({make: "Honda",    year: 1997, color: "maroon"});
cars.push({make: "Chrysler", year: 2000, color: "beige"});
cars.push({make: "Mercedes", year: 1985, color: "blue"});
cars.push({make: "Fiat",     year: 1983, color: "gray"});
// Sort the cars array according to the year property
// of each element.cars.sortOn("year");
for (var i:int = 0; i < cars.length; i++) {
/* Displays:
gray    1983  Fiat
blue    1985  Mercedes
maroon  1997  Honda
beige   2000  Chrysler
*/
trace(cars[i].color + "\\t" +
cars[i].year + "\\t" +
cars[i].make);
}```

The `sortOn( )` method also has the ability to sort on more than one field. You can do so by specifying an array of fields on which to sort. The elements are then sorted on those fields in the specified order. To understand how it works, take a look at the following examples:

```var cars:Array = new Array( );
cars.push({make: "Honda",    year: 1997, color: "maroon"});
cars.push({make: "Chrysler", year: 2000, color: "beige"});
cars.push({make: "Mercedes", year: 1985, color: "blue"});
cars.push({make: "Fiat",     year: 1983, color: "gray"});
cars.push({make: "Honda",    year: 1992, color: "silver"});
cars.push({make: "Chrysler", year: 1968, color: "gold"});
cars.push({make: "Mercedes", year: 1975, color: "green"});
cars.push({make: "Fiat",     year: 1983, color: "black"});
cars.push({make: "Honda",    year: 2001, color: "blue"});
cars.push({make: "Chrysler", year: 2004, color: "orange"});
cars.push({make: "Mercedes", year: 2000, color: "white"});
cars.push({make: "Fiat",     year: 1975, color: "yellow"});

// Sort the cars array according to the year property
// of each element, then by the make.
cars.sortOn(["year", "make"]);

for (var i:int = 0; i < cars.length; i++) {
/* Displays:
gold     1968    Chrysler
yellow   1975    Fiat
green    1975    Mercedes
black    1983    Fiat
gray     1983    Fiat
blue     1985    Mercedes
silver   1992    Honda
maroon   1997    Honda
beige    2000    Chrysler
white    2000    Mercedes
blue     2001    Honda
orange   2004    Chrysler
*/
trace(cars[i].color + "\\t" +
cars[i].year + "\\t" +
cars[i].make);
}```

The next example sorts the same array first by make, then by year--notice what the effect is:

```cars.sortOn(["make", "year"]);

for (var i:int = 0; i < cars.length; i++) {
/* Displays:
gold    1968    Chrysler
beige   2000    Chrysler
orange  2004    Chrysler
yellow  1975    Fiat
black   1983    Fiat
gray    1983    Fiat
silver  1992    Honda
maroon  1997    Honda
blue    2001    Honda
green   1975    Mercedes
blue    1985    Mercedes
white   2000    Mercedes
*/
trace(cars[i].color + "\\t" +
cars[i].year + "\\t" +
cars[i].make);
}```

As with the `sort( )` method, the `sortOn( )` method supports sort modifiers. You can use the `Array` constants to sort in descending, case-insensitive, and numeric order. You can also, as with the `sort( )` method, run a unique sort and return an array of sorted indices rather than affecting the original array. The following example sorts `cars` in descending order:

```cars.sortOn("year", Array.DESCENDING);

for (var i:int = 0; i < cars.length; i++) {
/* Displays:
beige   2000  Chrysler
maroon  1997  Honda
blue    1985  Mercedes
gray    1983  Fiat
*/
trace(cars[i].color + "\\t" +
cars[i].year + "\\t" +
cars[i].make);
}```

Sorted arrays can be useful in many scenarios. For example, if you want to display the elements of an array in a UI component or a text field, you often want to list the elements in alphabetical order.

Unless you use the `Array.RETURNINDEXEDARRAY` constant, the `sort( )` and `sortOn( )` methods make changes to the order of the original array; they do not return a new array.

Recipe 5.8 to make a separate copy of an array on which you can perform destructive operations. Recipe 5.11 for details on custom sorting.

## Section 5.11: Implementing a Custom Sort

### Problem

You want to sort an array using more complex logic than an alphabetical or numeric sort.

### Solution

Use the `sort( )` method and pass it a reference to a `compare function`.

### Discussion

If you want complete control over sorting criteria, use the `sort( )` method with a custom compare function (also called a sorter function). The `sort( )` method repeatedly calls the compare function to reorder two elements of an array at a time. It sends the compare function two parameters (let's call them a and b). The compare function then determines which one should be ordered first by returning a positive number, a negative number, or 0, depending on how the elements are to be sorted. If the function returns a negative number, a is ordered before b. If the function returns 0, then the current order is preserved. If the function returns a positive number, a is ordered after b. The `sort( )` method calls the compare function with every relevant combination of elements until the entire array has been properly ordered. Using a custom compare function is easier than it sounds. You don't need to concern yourself with the details of sorting the entire array; you simply specify the criteria for comparing any two elements.

One example of when you would want to use a custom sorter is when you need to sort a list of strings, but you need to process the strings somehow before sorting them. Say you are building a music program that needs to display a list of bands. If you just sorted the bands alphabetically, all the bands whose names began with "The" would appear together in the T section, which is probably not what you want. You can define a compare function that strips off "The" from the beginning of the name before comparing the bands. Here is the code to set up the array, perform a simple sort, and display the results:

```var bands:Array = ["The Clash",
"The Who",
"Led Zeppelin",
"The Beatles",
"Aerosmith",
"Cream"];
bands.sort( );
for(var i:int = 0; i < bands.length; i++) {
trace(bands[i]);

/* output:
Aerosmith
Cream
Led Zeppelin
The Beatles
The Clash
The Who
*/
}```

To handle this, call the `sort( )` method passing the `bandNameSort` compare function:

```var bands:Array = ["The Clash",
"The Who",
"Led Zeppelin",
"The Beatles",
"Aerosmith",
"Cream"];
bands.sort(bandNameSort);
for(var i:int = 0; i < bands.length; i++) {
trace(bands[i]);
/* output:
Aerosmith
The Beatles
The Clash
Cream
Led Zeppelin
The Who
*/
}

function bandNameSort(band1:String, band2:String):int
{
band1 = band1.toLowerCase( );
band2 = band2.toLowerCase( );
if(band1.substr(0, 4) == "the ") {
band1 = band1.substr(4);
}
if(band2.substr(0, 4) == "the ") {
band2 = band2.substr(4);
}
if(band1 < band2) {
return -1;
}
else {
return 1;
}
}```

The `bandNameSort( )` function first converts both band names to lowercase, ensuring a case-insensitive sort. Then it checks to see if either band name begins with "The ". If so, it grabs the portion of the string from the fourth character to the end, which is everything after the word "The" plus the space.

Finally, it compares the two processed strings, returning -1 if the first string should go first, and 1 if the first string should go second. As you can see, the output is more in line with what you would expect.

There is no limit to how complex the compare function can be. If you are sorting a list of objects, you can build in logic that reads multiple properties of each object, performs calculations on their data, compares them, and returns the results.

Realize that the compare function may be run hundreds or even thousands of times in a single sort of a large array, so be careful about making it too complex.

 Pages: 1, 2, 3, 4