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

## 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:

var names:Array = namesData.split(",");

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

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.