ActionScript Comparing Arrays - actionscript-3

how can i evaluate whether my test array is equal to my static constant DEFAULT_ARRAY? shouldn't my output be returning true?
public class myClass extends Sprite
{
private static const DEFAULT_ARRAY:Array = new Array(1, 2, 3);
public function myClass()
{
var test:Array = new Array(1, 2, 3);
trace (test == DEFAULT_ARRAY);
}
//traces false

Macke has already pointed out the problem. The == operator will tell you (for reference types such as Array objects) if two variables point to the same object. That's clearly not the case here. You have 2 different objects, that happen to have the same content.
So, what you're probably looking for is a way to compare whether 2 arrays have the same contents. This apparently simple task might be trickier than it seems.
The standard way is using a function like this:
function areEqual(a:Array,b:Array):Boolean {
if(a.length != b.length) {
return false;
}
var len:int = a.length;
for(var i:int = 0; i < len; i++) {
if(a[i] !== b[i]) {
return false;
}
}
return true;
}
This will work in some (arguably most) cases. But it will fail if the items in any of the arrays have reference type semantics (as opposed to value type semantics). Basically, Numbers (including ints and uints), Strings, Boolean, null and undefined have value type semantics:
Given:
var a:int = 0;
var b:int = 0;
This will hold true:
trace(a == b);
For everything else, comparing with == will only return true if both vars reference the same object:
var a:Object = {data:1};
var b:Object = {data:1};
trace(a == b); // false
But
var a:Object = {data:1};
var b:Object = a;
trace(a == b); // true
So, if your arrays contain objects (or in turn other Arrays), the areEqual will fail in this case:
var arr_1:Array = [{data:1}];
var arr_2:Array = [{data:1}];
trace(areEqual(arr_1,arr_2));
Why? Because the {data:1} object that you stored in arr_1 is different from the {data:1} object stored in arr_2.
I don't think it's possible to create a generic function that checks recursively whether two arrays' contents are equal, because there's no generic way of determinig whether two objects should be considered equal. This really depends on your objects, your domain, etc. I.e. in your app, two objects should be considered equal if they have the same ìd (assuming you have defined an ìd property). As I think this shows, there's no way to know before hand what makes to objects equal.

Dude, use the mx.utils.ObjectUtil... the creators of actionscript have already thought about this.
ObjectUtil.compare(this.instructions, other.instructions) == 0;

What you're doing there is comparing references, not values. This is because an array is not a value type. A number is a value type, for instance. A number with the value of 3 will always be equal to 3, no matter what references you compare. But a reference type, a block of memory accessed by reference, is just that, a block of memory. If you've got two references to the same block, they'll be considered equal. But again, you're really just comparing the references and not the actual values.
In your case, your conditional would be true if you had the following code:
private static const DEFAULT_ARRAY:Array = new Array(1, 2, 3);
public function myClass()
{
var test:Array = DEFAULT_ARRAY;
trace (test == DEFAULT_ARRAY);
}
//traces true
Which rather makes sense, since test is now referencing the same memory as is referenced by the constant. In order to make sure the arrays contain the same values, you need to loop through the array and compare the items:
private static const DEFAULT_ARRAY:Array = new Array(1, 2, 3);
public function myClass()
{
var test:Array = new Array(1, 2, 3);
var isEqual:Boolean = true;
for (var i:int = 0; i < test.length; i++)
{
if (test[i] == DEFAULT_ARRAY[i])
continue;
else
{
isEqual = false;
break;
}
}
trace (isEqual);
}
//traces true
Hamcrest could be useful in these types of scenarios.

Related

How can I check if an array contains another array?

At first sight it's very simple, but I'm having some problems to do this without using a lot of nested loops.
Example:
var father:Array = new Array(0,1,2,3,4,5);
var son:Array = new Array(3,4,5);
father.contains(son) // returns true or 4(the starting index if the contained array)
ActionScript 3 actually supports some slightly crazy stuff, due to the fact that, in the early days, Adobe/Macromedia were trying to make it compliant with Ecmascript.
So... you can do this:
var a1:Array = [1,2,3,4,5,6,7,8,9];
var a2:Array = [3,4,5];
// borrow String's indexOf function, and it magically works on Arrays
// but rename it because Array already has a different indexOf function
a1.indexOf2 = String.prototype.indexOf;
trace(a1.indexOf2(a2) > -1); // true
But you need to be a little bit careful because it will convert all the elements to Strings for the equality test. For primitives, it mostly won't matter but it will break badly with objects as they'll all be converted to "[object Object]" or to whatever their toString() returns.
Also, if you wanted to use the actual index for anything, rather than just checking it's not -1, you have to divide by two, as the number is double what you'd expect. I don't exactly know why this is :)
If you need something more general and reliable, you'd be better off writing a function to do an explicit search. This is a quick example, which I just wrote so could easily be bug-ridden:
public function find(haystack:Array, needle:Array):int
{
var index:int = -1;
while(index <= haystack.length - needle.length)
{
index++;
index = haystack.indexOf(needle[0], index);
for( var i:int = 1; i<needle.length; i++)
{
if(haystack[index+i] != needle[i])
{
continue;
}
}
if( i == needle.length)
{
return index;
}
}
return -1;
}
Try this for simplicity:
// Determines if an array contains en element (similar to the PHP function with the same name)
public function in_array(needle:*, haystack:Array):Boolean
{
for each (var element:* in haystack)
{
if (element == needle) {return true;}
}
return false;
}

How to implement createuniqueCopy() in actionscript 3?

I came across this thread:
Is there a function like array_merge in PHP in actionscript 3?
PHP has a built-in function that does this:
array array_merge ( array $array1 [, array $array2 [, array $... ]] )
And seems I need to include as3corelib library in order to use createuniqueCopy.
How to implement createuniqueCopy in order to reduce the size of the swf?
I would encourage you to use ArrayUtil as part of the as3core library. The file is only 5kb, and contains only a few functions so is very lightweight. If you import it, Flash is smart enough to only include the files it needs, so it won't bring in all the code from the entire as3core library.
On the other hand, if you really wanted to create your own, do something like:
var a1:Array = ["a", "b", "c"];
var a2:Array = ["b", "j", "e"];
var a3:Array = a1.concat(a2);
a3 = uniqueArray(a3);
trace(a3); // a,b,c,j,e
function uniqueArray(a:Array):Array {
var newA:Array = [];
for (var i:int = 0; i < a.length; i++) {
if(newA.indexOf(a[i]) === -1) newA.push(a[i]);
}
return newA;
}
Edit (question was clarified in comments)
If you are using associative arrays, Adobe recommends using Objects, not Arrays for associative arrays. See http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/Array.html
Adobe Do not use the Array class to create associative arrays (also called hashes), which are data structures that contain named elements instead of numbered elements. To create associative arrays, use the Object class. Although ActionScript permits you to create associative arrays using the Array class, you cannot use any of the Array class methods or properties with associative arrays.
So, tackle the problem like this:
var a1:Object = {};
var a2:Object = {};
a1["a"] = "the a";
a1["b"] = "the b";
a2["b"] = "the b2";
a2["c"] = "the c";
var a3:Object = uniqueArray(a1, a2);
for each(var i:* in a3) {
trace(i);
}
// outputs [the a, the b, the c]
// 'the b2' is omitted because 'b' key already exists
function uniqueArray(a1:Object, a2:Object):Object {
var newA:Object = {};
for(var i:String in a1) {
newA[i] = a1[i];
}
for(var j:String in a2) {
if(newA[j] === undefined) newA[j] = a2[j];
}
return newA;
}
1) you don't need to use the entire library, just import the arrayUtils class:
https://github.com/mikechambers/as3corelib/blob/master/src/com/adobe/utils/ArrayUtil.as
and then use the function:
var c:Array = ArrayUtil.createUniqueCopy(a1.concat(a2));
this will exclude duplicates, inorder to have exactly the same values with duplicates just remove:
if(ArrayUtil.arrayContainsValue(newArray, item)){
continue;
}
from the createUniqueCopy function
some examples:
Cleanly merge two arrays in ActionScript (3.0)?

AS3 Remove element from array (of objects) generically

Is there a way to generically remove an object from an array?
(maybe not using array.filter or creating a new array)
Example:
var arr:Array= new Array();
//create dummy objs
for (var i:uint=0; i < 10; i++){
var someObject:SomeClassObject = new SomeClassObject();
someObject.Name ="Amit"+ i;
someObject.Site="http://www.mysite.com/"+i;
//...many more props
arr.push(someObject);
}
//
removeElement("Amit4",arr);
removeElement("Amit8",arr);
//...so on so forth
Currently im using array.splice() to remove object
for (var i:Number=0; i < arr.length; i++)
{
if (arr[i].Name == element)
{
arr.splice(i, 1);
}
}
I want to write removeElement in such a way that i can use it for different
types of objects.
currently removeElement becomes dependant on implmentation..
Suppose if i want to remove a file from array of files given file name..i wud have to
again write "removeElement" by changing criteria.
Also may be i can vary the criteria varing criteria?
example :
arr= removeElement("Site","http://www.mysite.com/6",arr)
will remove object from arr whose "Site" property is equal to "http://www.mysite.com/6"
(using above example)
ie. removeElement(criteria:object,criteria_value(s):object,arr)
Thanks All.
Use
if(array.indexOf(obj) != -1)
array.splice(array.indexOf(obj),1);
I think the most flexible approach is the one followed by Array::filter. It's up to the caller to determine whether an item should be filtered out of the list or not, through a function callback.
Now, if you want to do it in place, you could write a simple function like this:
function remove(list:Array,callback:Function):Array {
for(var i:int = list.length - 1; i >= 0; i--) {
if(!callback(list[i])) {
list.splice(i,1);
}
}
return list;
}
This returns the list, as it could be convenient if you wanted to chain calls, but it acts on the array you passed instead of creating a new one.
Also note that it loops backwards. Otherwise, splice will get you bogus results.
You could use it like this:
var arr:Array = [1,2,9,10,455];
trace(arr);
function removeCallback(item:Number):Boolean {
return item < 10;
}
remove(arr,removeCallback);
trace(arr);
This way you are not restricted to equality (or inequality). The caller determines if the item should be kept or removed, by returning true or false respectively (to match filter). So, it's pretty much like filter, except it works in-place. If you want, you could also keep the same interface for the callback (passing the index of the item and a reference to the original array) to make it more coherent.
By the way, you can use strings as indices for an array, and then you can safely use the 'delete' keyword to delete an object from inside the "middle" (there's actually no "middle" in this situation :) of the array.
e.g.:
var arr:Array = new Array();
arr['o1'] = new Object();
arr['o1'].someproperty = true;
arr['o2'] = new Object();
arr['o2'].someproperty = true;
arr['o3'] = new Object();
arr['o3'].someproperty = true;
trace (arr['o2'].someproperty);
//Returns 'true'
trace (arr['o2']);
//Returns '[object Object]'
delete arr['o2'];
trace (arr['o2']);
//Returns 'undefined'
trace (arr['o2'].someproperty);
//Returns 'TypeError: Error #1010: A term is undefined and has no properties.'
The disadvantage is you won't be able to know the length of the array (arr.length will return 0), but you can of-course track it yourself...
Here is a generic function which will do what you want:
public static function removeItem(array: Array, propertyName: String, value: String): Array
{
var newArray: Array = [];
for (var index: int = 0; index < array.length; index++) {
var item: Object = array[index];
if (item && item.hasOwnProperty(propertyName)) {
if (item[propertyName] != value)
newArray.push(item);
}
}
return newArray;
}

AS3 - Is it possible to search an Array by Object properties?

Would it be possible to use Array.indexOf() to search through an Array by the properties of the Objects within the array:
var myArray:Array = new Array();
var myMovieClip = new MovieClip();
myMovieClip.name = "foo";
myArray.push(myMovieClip);
myArray.indexOf(MovieClip.name == "foo"); //0
OR
myArray.indexOf(myMovieClip.name == "foo"); //0
Both indexOf() above do not work, is there away to achieve this with the correct syntax?
Look into Array's filter method (newly available for AS3). You can write a filter method that returns all objects that will meet your criteria (in your case, a movieclip with a certain name)
index of will search for an entry ... MovieClip.name == "foo" should throw a compiler error, since MovieClip does not have a property "name" ... myMovieClip.name == "foo" will be true, and then you will get the index of true, if it is in the array at all ...
if you really need the index, you will need to iterate over the array ... by key ... or in an incremental loop, if the array is numeric and dense ...
if the array is associative (string keys used) you imperatively need to use for-in loops, since filter and related functions will only cover numeric indices ...
in a numeric array, i'd suggest one of the following two approaches:
//this will return an array of all indices
myArray.map(function (val:*,index:int,...rest):int { return (val.name == "foo") ? index : -1 }).filter(function (val:int,...rest):Boolean { return val != -1 });
//here a more reusable utility function ... you may want to put it to some better place ... just as an example ...
package {
public class ArrayUtils {
public static function indexOf(source:Array, filter:Function, startPos:int = 0):int {
var len:int = source.length;
for (var i:int = startPos; i < len; i++)
if (filter(source[i],i,source)) return i;
return -1;
}
}
}
//and now in your code:
var i:int = ArrayUtils.indexOf(myArray, function (val:*,...rest):Boolean { return val.name == "foo" });
hope that helped ... ;)
greetz
back2dos
Though back2dos' method is cool, I think beginners might find it overly-complicated, so for the sake of them here is a simpler method, that may be easier to use, though won't be as versatile for any situation as back2dos' method.
var myArray:Array = new Array();
var myMovieClip1 = new MovieClip();
var myMovieClip2 = new MovieClip();
myMovieClip1.name = "foo";
myMovieClip2.name = "bar";
myArray.push(myMovieClip1);
myArray.push(myMovieClip2);
function getIndexByName(array:Array, search:String):int {
// returns the index of an array where it finds an object with the given search value for it's name property (movieclips, sprites, or custom objects)
for (var i:int = 0; i < array.length; i++) {
if (array[i].name == search) {
return i;
}
}
return -1;
}
trace("bar index = "+getIndexByName(myArray, "bar"));
trace("foo index = "+getIndexByName(myArray, "foo"));
myMovieClip.name == "foo";
^== if you want to assign a variable, use
myMovieClip.name = "foo";
Here is what I did. Change the function name if you want... this is all just for the sake of explanation.
getObjectFromArrayByProperty(arrayToSearch, 'oBjectProperty', 'value');
function getObjectFromArrayByPoperty(array:Array, _property, search:String):Object{
for (var i:int = 0; i < array.length; i++) {
if (array[i][_property] == search) {
return array[i];
}
}
return null;
}
This returns the object instead of just an index. If a match is not found, the function returns null.

how do I add properties named by runtime arguments to an actionscript object?

I need a function that gets two array parameters of the same length: property-names (Strings) and property-values.
the function should create an object with properties so that, for an example, after calling
var obj:Object = makeObject({"prop1","prop2"},{1,2});
the testing condition (obj.prop1 == 1 && obj.prop2 == 2) should be true.
I'm led to believe that this should be an easy one if you know your actionscript - maybe it's just a syntax thing.
late addition
after re-reading my question, it appears It wasn't very easy to understand.
my problem was naming properties based on runtime values, that is, using a string parameter to refer a property name.
An Object can be treated like a map (or associative array) with strings for keys - I believe that's what you want to do. You can read up on associative arrays in Flex in Adobe's documentation.
private function makeObject( keys : Array, values : Array ) : Object
{
var obj : Object = new Object();
for( var i : int = 0; i < keys.length; ++i )
{
obj[ String(keys[i]) ] = values[i];
}
return obj;
}
This will create a new Object with keys equal to the values in the first array, and values equal to the items in the second array.
Not sure I understand your question, but you can create the Object using an Object literal:
var item:Object = {prop1: 1, prop2: 2};
trace (item.prop1 == 1 && item.prop2 == 2) // true