O'Reilly    
 Published on O'Reilly (http://oreilly.com/)
 See this if you're having trouble printing code examples


Arrays: Chapter 5 - ActionScript 3.0 Cookbook

by Joey Lott, Darron Schall, Keith Peters

This excerpt is from ActionScript 3.0 Cookbook. Well before Ajax and Windows Presentation Foundation, Macromedia Flash provided the first method for building "rich" web pages. Now, Adobe is making Flash a full-fledged development environment, and learning ActionScript 3.0 is key. That's a challenge for even the most experienced Flash developer. This Cookbook offers more than 300 solutions to solve a wide range of coding dilemmas, so you can learn to work with the new version right away.

buy button

Section 5.0: Introduction

Arrays are essential to successful ActionScript programming.

An array provides a way of grouping related data together, and then organizing and processing that data. The concept of an array should not be foreign to you. In fact, the concept is used all the time in everyday life. You can view a simple grocery list or to-do list as an array. Your address book is an array containing people's names, addresses, birthdates, and so on. Libraries keep track of books using an indexing system whereby each book becomes, conceptually, an element in a library's array.

In ActionScript, there are two kinds of arrays: integer-indexed and associative. Both array types group related data, but they use different means of accessing the data.

Integer-indexed array
Uses integers (numbers) as unique identifiers for each element in the array. Such arrays are ordered by index (i.e., element number), starting from 0. Each element occupies a numbered slot in the array. Integer-indexed arrays are ideal for sets of data that you want to work with in sequential order.
Associative array
Uses string keys to access each value. You can read more about associative arrays in Recipe 5.15.

Integer-indexed arrays are the focus of the majority of the recipes in this chapter, and unless otherwise specified, the term "array" refers to an integer-indexed array.

Of course, before you can use an array, you first need to know how to create one. There are two ways to construct a new array in ActionScript: with the constructor function or as an array literal. All arrays are members of the Array class. You can use the Array( ) constructor function to instantiate new array objects in one of three ways:

// Create an empty array.
var array:Array = new Array();

// Create an array with elements undefined elements.
var array:Array = new Array(elements); 

// Create an array with specified elements.
var array:Array = new Array(element0,...elementN);

The array literal notation also creates a new array, but without using the constructor function. Literal notation is convenient for specifying elements at the time of creation, such as:

var letters:Array = ["a", "b", "c"];

Some methods of the Array class modify the existing array on which the method is called, and others return a new array (offering an indirect way to create arrays).

You retrieve and set array elements using the array-access operator ( square brackets) and the index of the element you wish to get or set, for example:

// Set the fifth element of the array called items to "apples" 
// (array indexes start at 0).
items[4] = "apples";
     
// Display the fifth element in the Output window.
trace(items[4]);   // Displays: apples

ActionScript doesn't care what kind of values you store in an array. You can store strings, numbers, Booleans, or references to any kind of objects. And, unlike stricter programming languages, you can even store different datatypes in a single array. For example, this array stores a string, an integer, a Boolean, and an object:

var data:Array = ["a", 2, true, new Object()];

Unlike many languages, ActionScript doesn't force you to specify the number of elements in an array when it is declared.

Section 5.1: Adding Elements to the Start or End of an Array

Problem

You want to add elements to an existing array.

Solution

Use the push( ) method to append elements to the end of an array; use the unshift( ) method to insert elements at the beginning of an array.

Discussion

You append elements to the end of an existing array using the Array.push( ) method, passing it one or more values to be appended:

var array:Array = new Array();
array.push("val 1", "val 2");

You can also append a single element by using the array's length property as the index. Because ActionScript array indexes are zero-indexed (meaning that the first index is 0, not 1), the last element is at an index of Array .length - 1. Therefore, putting an element at index Array .length creates a new element right after the current last element; for example:

array[array.length] = "val 3";

If you set an element with an index that doesn't exist, the array extends itself to include the necessary number of elements automatically. If there are any intervening elements, they are initialized to undefined. For example, letters will contain the elements ["a", "b", "c", undefined, undefined, "f"] after you execute the following statements:

var letters:Array = ["a", "b", "c"];
letters[5] = "f";

Appending elements onto an array is common when you want to build an array incrementally or when you want to store the history of a user's actions for the purpose of implementing a back button or history feature.

To add elements to the beginning of an array, use the unshift( ) method. This shifts the existing elements up by one index position, and inserts the new element at index 0:

// Create an array with four elements:
// "a", "b", "c", and "d".
var letters:Array = new Array( );
letters.push("a", "b", "c", "d");
     
// Add "z" to the beginning of the array. This shifts all 
// the other elements so the value of "a" moves from 
// index 0 to index 1, etc.
letters.unshift("z");
     
// Display the results by looping through the elements. 
// See Recipe 5.2.
for (var i:int = 0; i < letters.length; i++) {
  trace(letters[i]);
}

Should you add elements to the beginning or the end of an array? That generally depends on how you intend to access or remove the elements at a later time. For example, if you want to access items in last in, first out (LIFO) order, you might use Array.push( ) to add elements to an array and Array.pop( ) to remove the elements in reverse order.

See Also

Recipe 5.2

Section 5.2: Looping Through an Array

Problem

You want to access each element of an array in sequential order.

Solution

Use a for loop that increments an index variable from 0 until it reaches Array. length. Use the index to access each element in turn.

Discussion

To access the values stored in the elements of an array, loop through the array's elements using a for loop. Because the first index of an array is 0, the index variable in the for statement should start at 0. The last index of an array is always 1 less than the length property of that array. Within the for statement, use the loop index variable within square brackets to access array elements. For example:

var letters:Array = ["a", "b", "c"];
for (var i:int = 0; i < letters.length; i++) {
  // Display the elements in the Output panel.
  trace("Element " + i + ": " + letters[i]);
}

The looping index variable (i in the example code) should range from 0 to one less than the value of the length property. Remember that the last index of an array is always one less than its length.

Alternatively, you can use a for statement that loops backward from Array. length -1 to 0, decrementing by one each time. Looping backward is useful when you want to find the last matching element rather than the first (see Recipe 5.3), for example:

var letters:Array = ["a", "b", "c"];
for (var i:int = letters.length - 1; i >= 0; i--){
  // Display the elements in reverse order.
  trace("Element " + i + ": " + letters[i]);
}

There are many instances when you might want to loop through all the elements of an array. For example, by looping through an array containing references to sprites, you can perform a particular action on each of the sprites:

for (var i:int = 0; i < sprites.length; i++){
  // Move each sprite one pixel to the right.
  sprites[i].x++;
}

You can store the array's length in a variable rather than computing it during each loop iteration. For example:

var length:int = sprites.length;
for (var i:int = 0; i < length; i++){
  // Move each sprite one pixel to the right.
  sprites[i].x++;
}

The effect is that there is a very marginal performance improvement because Flash doesn't have to calculate the length during each iteration. However, it assumes that you are not adding or removing elements during the loop. Adding or removing elements changes the length property. In such a case, it is better to calculate the length of the array with each iteration.

See Also

Recipe 12.8 for ways to loop through characters in a string. Recipe 5.16 for details on enumerating elements of an associative array. See also Recipe 5.3.

Section 5.3: Searching for Matching Elements in an Array

Problem

You want to find the first element in an array that matches a specified value.

Solution

Use a for statement to loop through an array and a break statement once a match has been found. Optionally, use the ArrayUtilities.findMatchIndex( ), ArrayUtilities.findLastMatchIndex( ), and/or ArrayUtilities.findMatchIndices( ) methods.

Discussion

When you search for the first element in an array that matches a specified value, you should use a for statement, as shown in Recipe 5.2, and add a break statement to exit the loop once the match has been found.

Using a break statement within a for statement causes the loop to exit once it is encountered. You should place the break statement within an if statement so it is executed only when a certain condition is met.

When searching for the first matching element, the importance of the break statement is twofold. First, you don't need to loop through the remaining elements of an array once the match has been found; that would waste processing time. In the following example, the break statement exits the loop after the second iteration, saving six more needless iterations. (Imagine the savings if there were a thousand more elements!)

Furthermore, the break statement is vital when searching for the first match because it ensures that only the first element is matched and that subsequent matches are ignored. If the break statement is omitted in the following example--all matching elements are displayed, as opposed to the first one only.

// Create an array with eight elements.
var letters:Array = ["a", "b", "c", "d", "a", "b", "c", "d"];
     
// Specify what we want to search for.
var match:String = "b";
     
// Use a for statement to loop through, potentially, 
// all the elements of the array.
for (var i:int = 0; i < letters.length; i++) {
     
    // Check whether the current element matches 
    // the search value.
    if (letters[i] == match) {
     
        // Do something with the matching element.
        // In this example, display a message 
        // for testing purposes.
        trace("Element with index " + i + 
              " found to match " + match);
     
        // Include a break statement to exit the for loop
        // once a match has been found.
        break;
    }  
}

You can also search for the last matching element of an array by reversing the order in which the for statement loops through the array. Initialize the index variable to Array .length -1 and loop until it reaches 0 by decrementing the index variable, as follows.

var letters:Array = ["a", "b", "c", "d", "a", "b", "c", "d"];
     
var match:String = "b";
     
// Loop backward through the array. In this example, 
// the "b" is at index 5.
for (var i:int = letters.length - 1; i >= 0; i--) {
    if (letters[i] == match) {
        trace("Element with index " + i + 
              " found to match " + match);
        break;
    }  
}

To simplify the process of searching for matching elements, you can use some of the static methods of the custom ArrayUtilities class. The class is in the ascb.util package, so the first step is to import the class:

import ascb.util.ArrayUtilities;

The ArrayUtilities class has three methods for finding matching elements-- findMatchIndex( ), findLastMatchIndex( ), and findMatchIndices( ). The findMatchIndex( ) method requires at least two parameters: a reference to the array you are searching, and the value you want to match. The method then returns either the index of the first matching element or -1 if no matches are found; for example:

var letters:Array = ["a", "b", "c", "d"];

trace(ArrayUtilities.findMatchIndex(letters, "b"));
// Displays: 1

trace(ArrayUtilities.findMatchIndex(letters, "r"));
// Displays: -1

You can also specify the starting index from which the search begins. That way, you can find matches subsequent to the first match. Specify the starting index as the third parameter; for example:

var letters:Array = ["a", "b", "c", "d", "a", "b", "c", "d"];

trace(ArrayUtilities.findMatchIndex(letters, "a", 1));
// Displays: 4

You can tell the method to find elements that are partial matches as well. By default, only exact matches are found. However, if you specify a value of true for the third parameter, the method finds any element containing the substring:

var words:Array = ["bicycle", "baseball", "mat", "board"];

trace(ArrayUtilities.findMatchIndex(words, "s", true));
// Displays: 1

If you want to run a partial match and still specify a starting index, simply pass the starting index as the fourth parameter.

The findLastMatchIndex( ) method works identically to findMatchIndex( ) except that it starts looking from the end of the array.

The findMatchIndices( ) method returns an array of indices for all elements that match the value passed in. The method requires at least two parameters--the array and the element you want to match. For example:

var letters:Array = ["a", "b", "c", "d", "a", "b", "c", "d"];

trace(ArrayUtilities.findMatchIndices(letters, "b"));
// Displays: 1,5

You can also run partial matches using findMatchIndices( ). Simply specify a Boolean value of true as the third parameter:

var words:Array = ["bicycle", "baseball", "mat", "board"];

trace(ArrayUtilities.findMatchIndices(words, "b", true));
// Displays: 0,1,3

Each of the ArrayUtilities methods described use the same basic techniques with a for statement. Let's take a look at the code for the methods. The findMatchIndex( ) method is fairly straightforward, and you can see the comments inline. One thing to note, however, is that the method doesn't use any break statements within the for loop. That's because it uses return statements if a match is found. In the context of a function or method, a return statement exits the for statement, so the break statement is not necessary:

public static function findMatchIndex(array:Array, element:Object):int {
    // Use a variable to determine the index 
    // from which to start. Use a default value of 0.
    var startingIndex:int = 0;

    // By default don't allow a partial match.
    var partialMatch:Boolean = false;

    // If the third parameter is a number, 
    // assign it to nStartingIndex.
    // Otherwise, if the fourth parameter is a number,
    // assign it to nStartingIndex instead.
    if(typeof arguments[2] == "number") {
        startingIndex = arguments[2];
    }    
    else if(typeof arguments[3] == "number") {
        startingIndex = arguments[3];
    }

    // If the third parameter is a Boolean value, 
    // assign it to partialMatch.
    if(typeof arguments[2] == "boolean") {
        partialMatch = arguments[2];
    }

    // Assume no match is found.
    var match:Boolean = false;

    // Loop through each of the elements of the array 
    // starting at the specified starting index.
    for(var i:int = startingIndex;
            i < array.length; i++) {

        // Check to see if the element either matches 
        // or partially matches.
        if(partialMatch) {
            match = (array[i].indexOf(element) != -1);
        }
        else {
            match = (array[i] == element);
        }

        // If the element matches, return the index.
        if(match) {
          return i;
        }
    }

    // The following return statement is only reached
    // if no match was found. In that case, return -1.
    return -1;
}

The findLastMatchIndex( ) method is almost identical to the findMatchIndex( ) method, except that it loops in reverse. The findMatchedIndices( ) method loops through the array to find every matching index. It appends each matching index to an array, and then it returns that array. It uses the findMatchIndex( ) method, as shown here:

public static function findMatchIndices(array:Array, 
element:Object, partialMatch:Boolean = false):Array {
    var indices:Array = new Array( );
    var index:int = findMatchIndex(array, 
                                   element,
                                   partialMatch);
    while(index != -1) {
        indices.push(index);
        index = findMatchIndex(array,
                               element,
                               partialMatch,
                               index + 1);
    }
    return indices;
}

See Also

Recipes 5.2 and 5.10

Section 5.4: Removing Elements

Problem

You want to remove one or more elements from an array and shift any remaining elements to fill the vacant indexes.

Solution

Use the splice( ) method to remove elements from the middle of the array. Use pop( ) to remove the last element or shift( ) to remove the first element.

Discussion

Remove elements from an array by starting at a specified index using the splice( ) method. When using splice( ) to delete elements, you should pass it two parameters:

start
The index of the array from which to start deleting elements.
deleteCount
The number of elements to delete. If this value is undefined, all the elements from start to the end of the array are deleted:
var letters:Array = ["a", "b", "c", "d"];
     
// Remove one element from letters starting at index 1.
letters.splice(1, 1);
     
// Display the results. The array now contains three elements:
// "a", "c", and "d".
for (var i:int = 0; i < letters.length; i++) {
    trace(letters [i]);
}

The splice( ) method also returns a new array containing the deleted elements; for example:

var letters:Array = ["a", "b", "c", "d"];
     
// Remove two elements from letters starting at index 0.
var deleted:Array = letters.splice(0, 2);
     
// Display the deleted elements: "a" and "b".
for (var i:int = 0; i < deleted.length; i++) {
    trace(deleted[i]);
}

To delete a single element from the beginning or end of the array, you can use the shift( ) and pop( ) methods. The shift( ) method removes the first element of the array and returns its value. The pop( ) method removes the last element of the array and returns its value:

var letters:Array = ["a", "b", "c", "d"];
     
// Remove the first element and display its value.
trace(letters.shift( ));
     
// Remove the last element and display its value.
trace(letters.pop( ));
     
// Display the remaining elements. 
// The array has two elements left: "b" and "c".
for (var i = 0; i < letters.length; i++) {
    trace(letters[i]);
}

When you remove elements from an array in a for statement, you need to change the value of the index variable accordingly. The following example illustrates what can happen if you don't update the value of the index variable:

var numbers:Array = new Array(4, 10);
numbers[4] = 1;
trace(numbers);  // Displays: 4,10,undefined,undefined,1
for(var i:int = 0; i < numbers.length; i++) {
    if(numbers[i] == undefined) {
        numbers.splice(i, 1);
    }
}
trace(numbers);  // Displays: 4,10,undefined,1

In the preceding code, you might have expected it to remove both of the undefined elements from the array. However, as shown in the final trace, it removed only one. If you go through the for statement step-by-step, you can see why:

  1. The first two iterations do nothing because the elements are not undefined.
  2. The third iteration sees that the third element is undefined and removes it. At that point, the fourth and fifth elements shift down by one index, becoming the third and fourth elements.
  3. The next iteration checks the new fourth element, which is now the last. It skips right over the other undefined element (now third). Instead, you can make sure you decrement the index variable after removing the element. The following code shows how you might do that:
  4. var numbers:Array = new Array(4, 10);
    numbers[4] = 1;
    trace(numbers);  // Displays: 4,10,undefined,undefined,1
    for(var i:int = 0; i < numbers.length; i++) {
      if(numbers[i] == undefined) {
        numbers.splice(i, 1);
        i--;
      }
    }
    trace(numbers);  // Displays: 4,10,1

Section 5.5: Inserting Elements in the Middle of an Array

Problem

You want to insert elements in the middle of an array.

Solution

Use the splice( ) method.

Discussion

You can use the splice( ) method to insert elements as well as delete them. Values passed to the splice( ) method after the first and second parameters are inserted into the array at the index specified by the start parameter; all existing elements following that index are shifted up to accommodate the inserted values. If 0 is passed to the splice( ) method for the deleteCount parameter, no elements are deleted, but the new values are inserted:

var letters:Array = ["a", "b", "c", "d"];
     
// Insert three string values ("one", "two", and "three")
// starting at index 1.
letters.splice(1, 0, "r", "s", "t");
     
// letters now contains seven elements:
// "a", "r", "s", "t", "b", "c", and "d".
for (var i:int = 0; i < letters.length; i++) {
    trace(letters[i]);
}

You can also delete elements and insert new elements at the same time:

var letters:Array = ["a", "b", "c", "d"];
     
// Remove two elements and insert three more 
// into letters starting at index 1.
letters.splice(1, 2, "r", "s", "t");
     
// myArray now contains five elements:
// "a", "r", "s", "t", and "d".
for (var i:int = 0; i < letters.length; i++) {
    trace(letters[i]);
}

Section 5.6: Converting a String to an Array

Problem

You have a list of values as a string and you want to parse it into an array of separate elements.

Solution

Use the String.split( ) method.

Discussion

The split( ) method of the String class splits a string containing a list of values into an array. The list must be delimited by a uniform substring. For example, the list Susan,Robert,Paula is comma-delimited.

The split( ) method takes up to two parameters:

delimiter
The substring that is used to delimit the elements of the list. If undefined, the entire list is placed into the first element of the new array.
limit
The maximum number of elements to place into the new array. If undefined, all the elements of the list are placed into the new array.

You can use a space as the delimiter to split a string into an array of words:

var list:String = "Peter Piper picked a peck of pickled peppers";
// Split the string using the space as the delimiter. This puts 
// each word into an element of the new array, words.
var words:Array = list.split(" ");

The split( ) method can be extremely useful when values are loaded into Flash using a URLLoader object or another similar technique for loading data. For example, you might retrieve a list of names as a string from the server such as the following:

names=Michael,Peter,Linda,Gerome,Catherine

You can make it easier to use the names by parsing them into an array using the split( ) method:

// Assume _loader is the URLLoader used to load the data.
var namesData:String = _loader.data;
var names:Array = namesData.split(",");

See Also

Recipe 5.7

Section 5.7: Converting an Array to a String

Problem

You want to convert an array to a string.

Solution

Use the join( ) method.

Discussion

ActionScript provides you with a built-in way to quickly convert arrays to strings (assuming, of course, that the array elements themselves are either strings or another datatype that ActionScript can automatically cast to a string) using the join( ) method. You should pass the join( ) method a string that tells Flash which delimiter to use to join the elements:

var letters:Array = ["a", "b", "c"];
trace(letters.join("|"));   // Displays: a|b|c

If you don't provide a delimiter, Flash uses a comma by default:

var letters:Array = ["a", "b", "c"];
trace(letters.join());   // Displays: a,b,c

The toString( ) method does the same thing as the join( ) method either with no parameters or with the comma as the parameter. In fact, if you try to use an array in a situation in which a string is required, Flash automatically calls the toString( ) method, as follows:

var letters:Array = ["a", "b", "c"];
trace(letters);  // Displays: a,b,c

See Also

Recipe 5.6

Section 5.8: Creating a Separate Copy of an Array

Problem

You want to make an exact copy (a duplicate) of an array--one that contains all of the elements found in the original, but is not just another reference to the original.

Solution

Use the concat( ) method or the slice( ) method. Optionally, you can use the ArrayUtilities.duplicate( ) method. The duplicate( ) method can create recursive duplicates.

Discussion

Because arrays are a composite datatype, they are copied and compared differently from primitive data. A variable that holds an array doesn't truly contain all of the array's data. Instead, the variable simply points to the place in the computer's memory where the array's data resides. This makes sense from an optimization standpoint. Primitive data tends to be small, such as a single number or a short string. But composite data, such as an array, can be very large. It would be inefficient to copy an entire array every time you wanted to perform an operation on it or pass it to a function. Therefore, when you try to copy an array, ActionScript doesn't make a separate copy of the array's data. A simple example illustrates this.

First, let's look at how primitive data is copied from the variable quantity to another variable, newQuantity:

// Assign the number 5 to a variable.
var quantity:int = 5;

// Copy quantity's value to another variable, newQuantity. 
var newQuantity:int = quantity;

// Change quantity's value.
quantity = 29;
     
trace(quantity);        // Displays: 29
trace(newQuantity);     // Displays: 5

When the copy is made, the contents of quantity are copied to newQuantity. After the copy is made, subsequent changes to quantity have no effect on newQuantity (and vice versa) because primitive data is copied by value.

Now let's look at a similar operation with arrays; however, note the difference from the preceding example. The variable letters is assigned to the variable newLetters, but the two variables merely reference the same array in memory. When the value of letters changes, the changes are reflected in newLetters:

// Assign elements of an array.
var letters:Array = ["a", "b", "c"];

// Copy letters to another variable, newLetters.
var newLetters:Array = letters;
     
// Both arrays contain the same values, as expected.
trace(letters);        // Displays: "a,b,c"
trace(newLetters);     // Displays: "a,b,c"
     
// Change letters's value.
letters = ["d", "e", "f"];
     
// Surprise! Both arrays contain the new values.
// The old values are lost!
trace(letters);        // Displays: "d,e,f"
trace(newLetters);     // Displays: "d,e,f" (not "a,b,c")

Is the relationship between two copies of an array a good thing or a bad thing? The answer depends on what you expect and what you need to accomplish. Let's first understand what is happening, and then learn how to address it.

In the preceding example, the following line does not make a copy of letters' contents, as it would if letters held a primitive datatype:

var newLetters:Array = letters;

Instead it says to Flash, "Make newLetters point to whatever letters points to, even if the contents change in the future." So the two variables letters and newLetters always point to the same data in memory. If it helps, you can think of this arrangement as being similar to a file shortcut on Windows (known as an alias on the Macintosh). A shortcut simply points to another file located elsewhere. Whether you open the original file directly or access it via the shortcut, there is only one physical file that contains the content of interest. If the file's contents change, the shortcut still offers access to the current contents of the file. If you wanted two independent files, you'd have to duplicate the original file rather than simply create a shortcut to it.

So, is it a good thing if two variables refer to the same array? As explained earlier, in the normal course of things, it increases efficiency to avoid copying the contents of an array unnecessarily. However, you might want to operate on a copy of an array and not alter the original. You can create a duplicate copy of an array that is separate from the original using concat( ):

// Assign elements of an array.
var letters:Array = ["a", "b", "c"];

// Create an independent copy of letters using concat( ),
// which returns a new array.
var newLetters:Array = letters.concat( );
     
// Both arrays contain the same values, as expected.
trace(letters);        // Displays: "a,b,c"
trace(newLetters);     // Displays: "a,b,c"
     
// Change letters' value.
letters = ["d", "e", "f"];
     
// Unlike preceding examples, the arrays are independent.
trace(letters);        // Displays: "d,e,f"
trace(newLetters);     // Displays: "a,b,c"

In line 6 of the preceding example, you could also use slice( ) instead of concat( ), as follows:

var newLetters:Array = letters.slice(0);

The concat( ) or slice( ) methods work fine to duplicate a single-dimensional, integer-indexed array. However, when you have a multidimensional array (an array containing other arrays) or an associative array, you cannot use those techniques effectively. (See Recipes 5.9 and 5.15 for more information regarding multidimensional and associative arrays, respectively.) With associative arrays, you won't have a concat( ) or slice( ) method. With multidimensional arrays, however, using concat( ) or slice( ) to duplicate the top level of the array won't duplicate the nested array data. The following code illustrates the effect:

var coordinates:Array = new Array( );
coordinates.push([0,1,2,3]);
coordinates.push([4,5,6,7]);
coordinates.push([8,9,10,11]);
coordinates.push([12,13,14,15]);

// Make a duplicate.
var coordinatesDuplicate:Array = coordinates.concat( );

// Replace one of the elements of one of the nested arrays
// in the duplicate.
coordinatesDuplicate[0][0] = 20;
trace(coordinates[0][0]);  // Displays: 20

// Replace one of the top-level elements.
coordinatesDuplicate[1] = [21,22,23,24];
trace(coordinates[1]);  // Displays: 4,5,6,7

In the preceding code, coordinates is an array of arrays; this is known as a two-dimensional array in ActionScript. coordinatesDuplicate is a duplicate of coordinates. However, even though it is a duplicate, its elements (which are also arrays) are still references to the original elements rather than duplicates. That means that if you assign a new value to one of the elements of one of the nested arrays in coordinatesDuplicate, coordinates is affected similarly. However, just to verify that coordinatesDuplicate does actually duplicate the top-level elements, you can see that in the last two lines of the code, replacing one of those elements does not affect coordinates.

To duplicate an array and ensure that every nested element is also duplicated, you need to use recursion. The ArrayUtilities.duplicate( ) method does just that, making it relatively simple for you to duplicate an array recursively. The duplicate( ) method requires just one parameter: a reference to an array or associative array. The method then returns a duplicate of that object. However, by default, duplicate( ) only returns a duplicate of the top-level elements, the same as concat( ) or slice( ). If you want to duplicate the instance recursively, you need to specify that using a second parameter. Specify a Boolean value of true to recursively duplicate an instance, as shown in the following example:

// Create a two-dimensional array.
var coordinates:Array = new Array( );
for(var i:int = 0; i < 4; i++) {
  coordinates[i] = new Array( );
  for(var j:int = 0; j < 4; j++) {
    coordinates[i].push(String(i) + "," + String(j));
  }
}

// Duplicate coordinates. Cast the result as an array.
var newCoordinates:Array = ArrayUtilities.duplicate(coordinates, true) as Array;

// Replace an element in the nested array.
newCoordinates[0][0] = "a";

// Use the toString() method of the ArrayUtilities class 
// to quickly output the contents of the arrays.
trace(ArrayUtilities.toString(coordinates));
trace(ArrayUtilities.toString(newCoordinates));

The following example illustrates the same duplicate( ) method used with an associative array:

var coordinatesMap:Object = new Object( );
coordinatesMap.a = [{a: 1},{b: 2}, {c: 3}, {d: 4}];
coordinatesMap.b = [{a: 1},{b: 2}, {c: 3}, {d: 4}];
coordinatesMap.c = [{a: 1},{b: 2}, {c: 3}, {d: 4}];
coordinatesMap.d = [{a: 1},{b: 2}, {c: 3}, {d: 4}];
var newCoordinatesMap:Object = ArrayUtilities.duplicate(coordinatesMap, true);
newCoordinatesMap.a[0] = {r: 5};
trace(ArrayUtilities.toString(coordinatesMap));
trace(ArrayUtilities.toString(newCoordinatesMap));

In both examples, you can see that the original array (or associative array) is not affected by changes made to the duplicate.

See Also

Recipes 5.9 and 5.15

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);
}

See Also

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.

See Also

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.

Section 5.12: Randomizing the Elements of an Array

Problem

You want to randomize the elements of an array.

Solution

Use the sort( ) method with a compare function that randomly returns a positive or negative number.

Discussion

There are lots of scenarios in which you might plausibly want to randomize the elements of an array. For example, you may have a game in which you want to randomize the letters of a word. Since you already know how to split the letters into elements of an array using the split( ) method, you may need to randomize those elements. Or perhaps you are making a card game where each element in the array is a card, and you want to shuffle the deck.

There is more than one way to accomplish the task. However, one of the simplest ways is to create a compare function that randomly returns a positive or negative number, and use it in the sort( ) method. See Recipe 5.11 for more information on compare functions.

The following is probably the simplest possible compare function for the job:

function randomSort(elementA:Object, elementB:Object):Number {
    return Math.random( ) - .5
}

Math.random( ) returns a number between 0.0 and 1.0. If you subtract 0.5 from that number, you'll get a random number between -0.5 and 0.5. Remember that in a compare function, returning a negative number means to order the first element first, and a positive number tells the sort( ) method to put the second element first. Since the odds you'll return are 50/50, the resulting order of the array is completely random.

The following is an example of randomizing an array:

var numbers:Array = new Array( );
for(var i:int=0;i<20;i++) {
    numbers[i] = i;
}
numbers.sort(randomSort);
for(var i:int=0;i<numbers.length;i++) {
    trace(numbers[i]);
}

This creates an array of 20 sequential numbers, and then randomizes and displays them. You can verify that the order is now quite random.

See Also

Recipe 5.11 for more information on compare functions.

Section 5.13: Getting the Minimum or Maximum Element

Problem

You want to retrieve the minimum or maximum element from an array of numbers.

Solution

Sort the array numerically, and then retrieve the first or last element from the sorted array.

Discussion

You can quickly retrieve the minimum or maximum value from an array by sorting it. The following example illustrates how to do just that:

var scores:Array = [10, 4, 15, 8];
scores.sort(Array.NUMERIC);
trace("Minimum: " + scores[0]);
trace("Maximum: " + scores[scores.length - 1]);

Of course, if the existing order of the array is important, you'll want to make a copy of the array before sorting it. See Recipe 5.8.

You can optionally use the ArrayUtilities.min( ) and ArrayUtilities.max( ) methods.

See Also

Recipe 5.8

Section 5.14: Comparing Arrays

Problem

You want to compare two arrays to see if they are equivalent.

Solution

Loop through each element of both arrays and compare them.

Discussion

Since arrays are reference datatypes, using an equality operator with two array variables only checks to see if they point to the same spot in memory. For example:

var letters:Array = ["a", "b", "c", "d"];
var lettersPointer:Array = letters;
trace(letters == lettersPointer);  // Displays: true

However, if two arrays are equivalent yet don't point to the same spot in memory, an equality operation returns false:

var letters1:Array = ["a", "b", "c", "d"];
var letters2:Array = ["a", "b", "c", "d"];
trace(letters1 == letters2];  // Displays: false

Instead, you can loop through each of the elements of the arrays and compare them:

var equivalent:Boolean = true;
for(var i:int = 0; i < letters1.length; i++) {
    if(letters1[i] != letters2[i]) {
        equivalent = false;
        break;
    }
}
trace(equivalent);  // Displays: true

Optionally, you can use the ArrayUtilities.equals( ) method. This method requires two parameters: the references to the two arrays to compare. The method returns a Boolean value that indicates whether the arrays are equivalent or not:

var letters1:Array = ["a", "b", "c", "d"];
var letters2:Array = ["a", "b", "c", "d"];
trace(ArrayUtilities.equals(letters1, letters2));
// Displays: true

By default, the order of the elements has to match in the two arrays. If you don't care whether the order of the elements matches, you can specify a third parameter for the equals( ) method; a Boolean value indicating whether or not the order should be disregarded:

var letters1:Array = ["a", "b", "c", "d"];
var letters2:Array = ["b", "a", "d", "c"];
trace(ArrayUtilities.equals(letters1, letters2));
// Displays: false
trace(ArrayUtilities.equals(letters1, letters2, true));
// Displays: true

The equals( ) method is fairly simple. The code is explained in the comments in the following code block:

public static function equals(arrayA:Array, 
                              arrayB:Array,
                              bNotOrdered:Boolean):Boolean {

    // If the two arrays don't have the same number of elements,
    // they obviously are not equivalent.
    if(arrayA.length != arrayB.length) {
        return false;
    }

    // Create a copy of each so that anything done to the copies 
    // doesn't affect the originals.
    var arrayACopy:Array = arrayA.concat( );
    var arrayBCopy:Array = arrayB.concat( );

    // If the order of the elements of the two arrays doesn't 
    // matter, sort the two copies so the order of the copies 
    // matches when comparing.
    if(bNotOrdered) {
        arrayACopy.sort( );
        arrayBCopy.sort( );
    }

    // Loop through each element of the arrays, and compare them. 
    // If they don't match, delete the copies and return false.
    for(var i:int = 0; i < arrayACopy.length; i++) {
        if(arrayACopy[i] != arrayBCopy[i]) {
            delete arrayACopy;
            delete arrayBCopy;
            return false;
        }
    }

    // Otherwise the arrays are equivalent. 
    // So delete the copies and return true.
    delete arrayACopy;
    delete arrayBCopy;
    return true;
}

Section 5.15: Creating an Associative Array

Problem

You want to create an array that uses named elements instead of numbered indexes.

Solution

Create an associative array.

Discussion

When working with sets of data in which each element has a specific meaning or importance, a typical, number-indexed array doesn't always suffice.

For example, if you are working with a set of data such as the names of members of a committee, a number-indexed array is sufficient:

var aMembers:Array = new Array("Franklin", "Gina", "Sindhu");

However, if each member of the committee plays a special role, a standard array offers no way to indicate that. To address the issue, you can use an associative array. In some languages, this is called a hash table. In ActionScript, it is actually just an instance of the Object class. An associative array uses named elements rather than numeric indexes. The names used to refer to elements are often called keys or properties. The keys can give a meaningful context to the associated element value.

You can create an associative array in ActionScript by using object literal notation or adding elements to an object. Despite their name, you don't use the Array class to create associative arrays. The Array class provides methods and properties that work with number-indexed arrays only--and not with associative arrays. Associative arrays should be instances of the Object class. Technically, since the Object class is the base class for all ActionScript classes, all ActionScript objects can be used as associative arrays. However, unless you have some specific reason for using another class as an associative array, it is best to simply use the generic Object class.

One way you can create an associative array is by using object literal notation. With this technique, use curly braces ({ }) to enclose a comma-delimited list of keys and values, which are separated by a colon (:), as shown in the following example:

var memebers:Object = {scribe: "Franklin",
                       chairperson: "Gina",
                       treasurer: "Sindhu"};

You can also create an associative array using the following multiline technique with the Object constructor. Although the object literal notation is fine for creating small associative arrays in a single step, you should use the Object constructor technique for creating larger associative arrays. It improves readability and lets you add properties to an associative array by assigning the properties (keys) on subsequent lines. For example:

var members:Object = new Object( );
members.scribe = "Franklin";
members.chairperson = "Gina";
members.treasurer = "Sindhu";

Although using an Object constructor is more common, you can initialize the associative array object by using an empty object literal in place of the Object constructor:

var members:Object = {};

You can retrieve the values from an associative array in two ways. The first way is to access the elements using property notation (with the dot operator):

trace(members.scribe); // Displays: Franklin

The other option for retrieving values from an associative array is using array-access notation. To use array-access notation, reference the associative array followed by the array-access operator ([ ]). Within the array-access operator, you must use the string value of the name of the key you wish to access:

trace(members["scribe"]); // Displays: Franklin

Array-access notation is extremely useful in situations in which there are multiple keys with names in a sequence. This is because you can dynamically generate the key string value, whereas you cannot do this with property notation; for example:

var members:Object = new Object();
members.councilperson1 = "Beatrice";
members.councilperson2 = "Danny";
members.councilperson3 = "Vladamir";
     
for (var i:int = 1; i <= 3; i++) {
    trace(members["councilperson" + i];
}

Array access notation is most frequently used when looping through every element in an associative array, as shown in Recipe 5.16.

You can use either the property notation or array-access notation to read or write the values of an associative array:

var members:Object = new Object( );
members["councilperson"] = "Ruthie";
trace(members.councilperson);         // Displays: Ruthie
members.councilperson = "Rebecca";
trace(members["councilperson"]);      // Displays: Rebecca

See Also

Recipe 5.16 contains more details on accessing named elements of an associative array.

Section 5.16: Reading Elements of an Associative Array

Problem

You want to loop through the elements of an associative array.

Solution

Use a for . . . in statement.

Discussion

You iterate through the elements of integer-indexed arrays by using a for statement. However, named elements in associative arrays cannot be accessed by a numeric index, and the order of associative array elements is not guaranteed, regardless of the order in which the elements are added to the array. For that reason, there are also no methods to sort or reverse an associative array, or otherwise change its order.

Fortunately, you can loop through the enumerable elements of an associative array by using a for . . . in statement. This statement iterates through all the readable properties of the specified object. The syntax for a for . . . in statement is as follows:

for (key in object) {
    // Actions
}

The for . . . in statement doesn't require an explicit update statement because the number of loop iterations is determined by the number of properties in the object being examined. Note that key is a variable name that will be used to store the property name during each iteration, not the name of a specific property or key. On the other hand, object is the specific object whose properties you want to read. For example:

var members:Object = new Object( );
members.scribe = "Franklin";
members.chairperson = "Gina";
members.treasurer = "Sindhu";
     
// Use a for . . . in statement to loop through all elements.
for (var sRole:String in members) {
    // Displays:
    // treasurer: Sindhu
    // chairperson: Gina
    // scribe: Franklin
    trace(sRole + ": " + members[sRole]);
}

When you use a for . . . in statement, you must use array-access notation (square brackets) with the associative array. If you try to use property notation (with the dot operator) it won't work properly. This is because the value that is assigned to the key iterator variable is the string name of the key, not the key's identifier.

A for . . . in loop does not display all built-in properties of an object. For example, it displays custom properties added at runtime, but it does not enumerate methods of built-in objects, even though they are stored in object properties.

See Also

Recipe 5.2

This excerpt is from ActionScript 3.0 Cookbook. Well before Ajax and Windows Presentation Foundation, Macromedia Flash provided the first method for building "rich" web pages. Now, Adobe is making Flash a full-fledged development environment, and learning ActionScript 3.0 is key. That's a challenge for even the most experienced Flash developer. This Cookbook offers more than 300 solutions to solve a wide range of coding dilemmas, so you can learn to work with the new version right away.

buy button

Copyright © 2009 O'Reilly Media, Inc.