Recipe 5.8. Creating a Separate Copy of an Array


Problem

You want to make an exact copy (a duplicate) of an arrayone 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




ActionScript 3. 0 Cookbook
ActionScript 3.0 Cookbook: Solutions for Flash Platform and Flex Application Developers
ISBN: 0596526954
EAN: 2147483647
Year: 2007
Pages: 351

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net