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
|