How to implement createuniqueCopy() in actionscript 3? - 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)?

Related

Variables in object names

Is it possible to get the child of an object with variables IN the object instance name?
location_1, location_2 are MCs containing hidden_1, hidden_2 MCs etc.
And I'd ideally like to target the hidden objects with increasing integers inside a for loop. I've removed the for loop for ease of reading in the below:
var i = 0;
var cacheNum = 0;
var locMc = this["location_"+(i+1)]; // This works
var hiddenMc = locMc.this["hidden_"+(cacheNum+1)]; // This doesn't work!
What I'd ideally like to be possible is:
var i = 1;
var cacheNum = 1;
var hiddenMc = location_i.hiddenMc_cacheNum;
Many thanks,
Nick
The syntax for object access by name is the same as array access by number:
object[value];
In the case of arrays, the value will be a number:
var myArray:Array = ["a", "b", "c"];
myArray[0]; // "a"
In case of objects, the value will be a string:
var myObject:Object = {"a": 1, "b": 2, "c": 3}
myObject["a"] // 1
When accessing variables in the current object, you must use this, as in your example. But when using different objects, you just use the bracket notation directly:
var hiddenMc = locMc["hidden_"+(cacheNum+1)];
If the object is a DisplayObjectContainer (like Sprite or MovieClip) and the value you are accessing is a child (as in your case), you can also use the getChildByName method:
var hiddenMc = locMc.getChildByName("hidden_"+(cacheNum+1));
Like any expression, you can string accesses this way:
this["location_"+(i+1)]["hidden_"+(cacheNum+1)]
But as you can see, this is not readable and very error prone. The best way would be to store those hidden MC's in an array and access them from there. It simplifies the code because you don't need to append "hidden_" when accessing them, is usually faster and will never clash with other children with similar names.
You are attempting to bypass encapsulation by allowing code to manipulate the inner workings of an object. Do that at your own peril. Instead, provide some way for the outside object to have access on a limited basis or to be able to ask the object containing those values to perform work upon them on the caller's behalf:
public class Hidden()
{
private var _hiddenValue:int = 5;
public function get hiddenValue():int
{
return _hiddenValue;
}
public function screwWithTheHiddenValue():void
{
_hiddenValue += 25;
}
}
public class Nosey()
{
var hidden:Hidden = new Hidden();
var result:int = hidden.hiddenValue;
// result should be 5
hidden.screwWithTheHiddenValue();
result = hidden.hiddenValue;
// result should now be 30;
}

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;
}

ActionScript Comparing Arrays

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.

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