views:

179

answers:

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

found out on another site the way to compare arrays is to convert them to strings, then compare.

trace (test.toString() == DEFAULT_ARRAY.toString());
//traces true
TheDarkInI1978
Converting to string will probably fail for any non-value type. You could have two arrays looking like this:var foo:Array = [{foo: 1}, {foo: 1}];var bar:Array = [{bar: 1}, {bar: 1}];If you were to convert this to strings, you'd get: [Object],[Object] and [Object],[Object]. This would make your logic fail. Now, if you were to override toString with some sort of hashing, you might be in business. Then again, if all you're ever working on is numbers, you might be fine.
macke
+2  A: 

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.

macke
+1. Your function will work only if the elements in the arrays are themselves value types, though.
Juan Pablo Califano
Absolutely true, but in my experience it's generally enough to compare references instead of trying to define some sort of deep comparison method (which is likely to be really slow any way). It's unfortunate though that ActionScript has no concept of custom value types. FWIW, I reported it as a feature request, but given how slow Adobe implements new AS features it's unlikely to happen anytime soon: https://bugs.adobe.com/jira/browse/ASC-3993
macke
Agreed about the slowness of this theorical deep comparation. The thing, anyway, is that "equality" is often really domain dependent; two objects might have a different value for one of its fields (a timestamp, for example) and still be considered equal in an app (but not equal in another).
Juan Pablo Califano
Re your feature request: Don't hold your breath! Yep, Macromedia/Adobe have been quite slow to fix notorious bugs (wmode + accents in non-IE plugin, I'm looking at you). Let alone implementing new feature requests. I'm sure you are aware of this, but in the case of Points you can `clone()` them to avoid *some* of the problems you mention. You could use the same approach for your own classes as well. It's not the same as having custom value types, but at least could help in that particular scenario.
Juan Pablo Califano
True, I didn't think about the semantics of equality. Regarding clone, we've been implementing that solution several times before but it's brittle. Other ways have been to let Flash serialize objects and then deserializing them to get a copy. Either way you're right as usual of course, holding my breath will probably cause me to die of suffocation ;o)
macke
+2  A: 

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.

Juan Pablo Califano
You're absolutely right, and it gets worse since you can't define custom value types in AS. You could implement a hashCode method or override toString to return a hash. That way the OP's original idea of converting the Array to a string might work. Although, that means that each object defines it properly, like you say with the id property with the difference that the hashCode method would calculate the hash on the fly where as the id would have to be set (unless it itself is calculated of course, but then the semantics is off I'd say).
macke
makes sense. interestingly, i couldn't successfully compare matching arrays of numbers, using your general method, if the arrays included NaN. so i changed if(a[i] !== b[i]) to if(a[i].toString() != b[i].toString())
TheDarkInI1978
@macke. Agreed. toString() will work for "primitives" (meaning, any object that has value type semantics, such as Strings). Another theoretically candidate would be valueOf(), since it's meant to return the primitive value of the object. But as far as I know, this works only for "primitives" and the Date object out of the box.
Juan Pablo Califano
@TheDarkIn1978. Right, I forgot NaN is a special case that defies logic... Well, there must be sound reasons for this (I read about it once), but the problem here is that `trace(NaN == NaN)` returns false. If `NaN` is involved, the `isNaN()` method should be used: `trace(isNaN(NaN) == isNaN(NaN))`. You could see if the item to check is a number and add the extra logic. But, again, if you know you arrays will contain only numbers, strings or booleans, the toString() method is shorter and it will work. Keep in mind it will give you bogus results for arrays that contain other objects, though.
Juan Pablo Califano
do all objects have a relatively dissimilar bytesize that could be compared?
TheDarkInI1978
I'm not sure, but I seem to remember that the "broken" NaN behavior is actually part of the ECMAScript spec. I think I heard that in some google tech talk about JavaScript a while back.
macke
@TheDarkIn1978. Bytesize is not going to work here. Two objects of the same class (given they're not `dynamic`) will have the same size and can have different contents. Worse, two objects of different types can happen to take up the same ammount of memory.
Juan Pablo Califano
@macke. Yes, even though it's counterintuitive, there's a reason for it (and a good one I think, I just can't remember what it is, although I remember having read about it a while ago).
Juan Pablo Califano
are the only reference data types in AS3 Array and Object?
TheDarkInI1978
@TheDarkIn1978. No, quite the opposite. Except for numbers (ints and uints included), Boolean and String every other type has reference semantics.
Juan Pablo Califano
ok. so all primitive types: Number, int, uint, Boolean and String are value types, and every other non primitive type is a reference type. right?
TheDarkInI1978
@TheDarkIn1978. Right. I didn't outright use the word "primitive" because actually there are no real primitives in AS (as there are in Java or C#, for instance). But numbers, booleans, and strings behave like if they were primitives in that they have value type semantics. You can treat them like values as opposed to references to objects stored somewhere else.
Juan Pablo Califano