0

I'm trying to create a function that will work for any array-like object in Flash but I'm really struggling to find a way to let the compiler know what I'm doing. I need to use functions like indexOf on the argument, but unless it is cast to the correct data type the compiler doesn't know that method is available. It's frustrating because Vector and Array share pretty much the same interface but there isn't an Interface to reflect that!

At the moment I've got this:

private function deleteFirst(tV:* , tVal:*):void {
  trace(tV)
  var tIndex:int
  if (tV is Array) {
    var tArray:Array = tV as Array
    tIndex = tArray.indexOf(tVal)
    if (tIndex >= 0) tArray.splice(tIndex, 1)
  } else if (tV is Vector.<*>) {
    var tVObj:Vector.<*> = tV as Vector.<*>
    tIndex = tVObj.indexOf(tVal)
    if (tIndex >= 0) tVObj.splice(tIndex, 1)
  } else if (tV is Vector.<Number>) {
    var tVNum:Vector.<Number> = tV as Vector.<Number>
    tIndex = tVNum.indexOf(tVal)
    if (tIndex >= 0) tVNum.splice(tIndex, 1)
  } else if (tV is Vector.<int>) {
    var tVInt:Vector.<int> = tV as Vector.<int>
    tIndex = tVInt.indexOf(tVal)
    if (tIndex >= 0) tVInt.splice(tIndex, 1)
  } else if (tV is Vector.<uint>) {
    var tVUInt:Vector.<uint> = tV as Vector.<uint>
    tIndex = tVUInt.indexOf(tVal)
    if (tIndex >= 0) tVUInt.splice(tIndex, 1)
  }
  trace(tV)
}

It kind of works but it's not exactly elegant! I'm wondering if there's a trick I'm missing. Ideally I'd do this by extending the base class, but I don't think that's possible with Vector.

Thanks

  • Thanks to everyone for your comments. I take the point about mixing Vectors and Arrays but I think in cases like this it's probably safe enough. It didn't occur to me to literally just invoke the functions without the compiler knowing in advance what the object types were! It's kind of obvious now you say it. – Danny Kodicek Sep 04 '15 at 15:53
  • If you were able to use one of the answers, do mark it so others can see how you approached the solution. Otherwise, if you went a different direction and solved it, do post your own answer. – Joseph Sep 06 '15 at 15:05

2 Answers2

0

This is definitely a short-coming of AS3, I don't think there is any elegant solution.

However, one code simplification you can make:

Since the syntax for indexOf() and splice() is the same for both arrays and vectors, you don't need that big if/else ladder to cast every type. You can simply call indexOf() and splice() on the object without any casting. Of course, you don't get any code-hints in your IDE, but it will work the same as you currently have. Example:

function deleteFirst(arrayOrVector:* , searchValue:*):* {
    if (arrayOrVector is Array || arrayOrVector is Vector.<*> || arrayOrVector is Vector.<Number> || arrayOrVector is Vector.<int> || arrayOrVector is Vector.<uint>) {
        var index:int = arrayOrVector.indexOf(searchValue)
        if (index >= 0)
            arrayOrVector.splice(index, 1)
    }else
        throw new ArgumentError("Argument 'arrayOrVector' must be an array or a vector, but was type " + getQualifiedClassName(arrayOrVector));

    return arrayOrVector;
}

You can even skip the whole if/else type check and it would still work, it would just make the code more confusing, and you would get a slightly more confusing error if you called the function with an argument other than array or vector (like "indexOf not found on type Sprite" if you passed a sprite object by accident).

Also it's worth mentioning that, while this doesn't help you with number base type vectors, with other vectors you can sort of use Vector.<*> as a generic vector reference. You can assign a reference using the Vector global function with wildcard (Vector.<*>(myVector)) and it will return a reference to the original vector instead of a new copy as it usually does. If you don't mind returning a copy of number based type vectors instead of always modifying the original vector, you can still take advantage of this to simplify your code:

function deleteFirst(arrayOrVector:* , searchValue:*):* {
    if (arrayOrVector is Array) {
        var array:Array = arrayOrVector;
        var index:int = array.indexOf(searchValue)
        if (index >= 0)
            array.splice(index, 1)
        return array;
    }else if(arrayOrVector is Vector.<*> || arrayOrVector is Vector.<Number> || arrayOrVector is Vector.<int> || arrayOrVector is Vector.<uint>) {
        var vector:Vector.<*> = Vector.<*>(arrayOrVector);
        index = vector.indexOf(searchValue);
        if (index >= 0)
            vector.splice(index, 1);
        return vector;
    }
    throw new ArgumentError("Argument 'arrayOrVector' must be an array or a vector, but was type " + getQualifiedClassName(arrayOrVector));
}
Aaron Beall
  • 49,769
  • 26
  • 85
  • 103
0

I would be very careful about mixing and matching Vectors and Arrays. The biggest difference between them is that Arrays are sparse, and Vectors are dense.

That said, here is your very compact generic removal function that will work on ANY "set" class that has indexOf and splice...

function deleteFirst( set:Object, elem:Object ) : Boolean
{
    if ( ("indexOf" in set) && ("splice" in set) )
    {
        var idx:int = set.indexOf( elem );
        if ( idx >= 0 )
        {
            set.splice( idx, 1 );
            return true;
        }
    }

    return false;
}

You can test the code with this code

        var arr:Array = [ 1, 2, 3, 4, 5 ];
        var vec:Vector.<int> = new Vector.<int>();
        vec.push( 1, 2, 3, 4, 5 );
        deleteFirst( arr, 2 );     // will remove 2
        deleteFirst( vec, 3 );     // will remove 3
        deleteFirst( "aaa4", "4" );   // nothing, cuz String doesn't have splice

        trace( arr );
        trace( vec );

UPDATE - For @Arron only, I've made the below change. Note that getting exceptions is good. They are informative and help reveal issues with the code path.

function deleteFirst( set:Object, elem:Object ) : Boolean
{
    var idx:int = set.indexOf( elem );
    if ( idx >= 0 )
    {
        set.splice( idx, 1 );
        return true;
    }
    return false;
}

There! Now it's even simpler. You get an exception that tells you what's wrong!

Joseph
  • 418
  • 2
  • 9
  • `deleteFirst({indexOf: 1, splice: 2}, 1)` throws `TypeError: Error #1006: indexOf is not a function.` – Aaron Beall Sep 04 '15 at 14:27
  • @Aaron, you're purposefully sending in garbage. – Joseph Sep 04 '15 at 14:30
  • All bugs can be described the same way. ;) – Aaron Beall Sep 04 '15 at 14:32
  • @Aaron - haha! ok then, if you want better, I'll revise – Joseph Sep 04 '15 at 14:32
  • @Aaron, now I don't bother with checking, so the code is even simpler. Note that your comment relates to your code, which specifically accepts only Arrays or Vectors. – Joseph Sep 04 '15 at 14:43
  • Yes, I know that, that's why I made more obvious the point of my code. Note that your code also throws an exception if you pass in "garbage". It does it in a "custom" way, whereas my code states specifically what went wrong. Horses for courses, so to speak! – Joseph Sep 04 '15 at 15:04
  • No, my code doesn't accept garbage. It will tell you it requires an array or vector (see the `ArgumentError`?). Your code assumes that if it has `indexOf` and `splice` that they are functions and that they behave a certain way. That's likely for sure, but an assumption. – Aaron Beall Sep 04 '15 at 15:06
  • If the requirement is to pass an array or vector, how is throwing an error that says "requires an array or vector" when it's not an array or vector *less* specific than a vague "indexOf is not a function" thrown by the runtime? – Aaron Beall Sep 04 '15 at 15:12
  • Aaron, you're missing a number of things. 1) I preface that you shouldn't be mixing the two objects blindly, because they have very different behaviors. 2) I address the issue in an alternative way that solves the problem and demonstrates how poorly typed languages behave if you want to base off signatures instead of classes. 3) Passing garbage into your function throws a custom Exception. Most importantly, this is NOT a complex question, and you're digging into other answers in a way suggesting you should just comment on YOURs like this, "if this answers your question, please accept it" :) – Joseph Sep 04 '15 at 15:14
  • Well I admit I may not understand your point. If it's that AS3 can't handle this well, and there's no great solution, only a range of hacks, I agree. :) – Aaron Beall Sep 04 '15 at 15:20
  • Yes, we're agreed on that. The question brings up one of my points that Arrays and Vectors shouldn't be used interchangeably, as they are very different beasts. Even before code, you will be making a lot of assumptions if you want to treat them equally, and will get the "worst" of both. The OP's desire to use common signatures reveals the problem. They weren't meant to be seen as one, and the language has made it just difficult enough to put it in the hack realm. – Joseph Sep 04 '15 at 15:42