Calculating Dictionary length in Flex - actionscript-3

What's the best way to calculate the length of a Dictionary object in Flex?
var d:Dictionary = new Dictionary();
d["a"] = "alpha";
d["b"] = "beta";
I want to check the length which should be 2 for this Dictionary. Is there any way to do it other than looping through the objects?

No, there is no way to check the length of an object(Dictionary is pretty much an object that supports non-String keys) other than looping through the elements.
http://www.flexer.info/2008/07/31/how-to-find-an-objects-length/
You probably don't have to worry about checking if the property is an internal one.

There's a util function in as3corelib which can get the keys in the dictionary. You can check out DicitonaryUtil
The method is:
public static function getKeys(d:Dictionary):Array
{
var a:Array = new Array();
for (var key:Object in d)
{
a.push(key);
}
return a;
}
So you would do getKeys(dictionary).length

You can use associative arrays instead because I don't think it's possible to check the length of a Dictionary object. You could however extend the Dictionary class and add that functionality and override the corresponding methods.
Alternatively, you could loop through it each time to get the length which isn't really a good idea but is available.
var d:Dictionary = new Dictionary();
d["hi"] = "you"
d["a"] = "b"
for (var obj:Object in d) {
trace(obj);
}
// Prints "hi" and "a"
You can also look here for information on using the "setPropertyIsEnumerable" but I believe that's more useful for objects than it is for Dictionary.

You could write a class around a dictionnary that controls insertions/removals so you can keep track of the key count.
Try extending proxy or just do a wrapper.

For anyone stumbling upon this now there is an update to DictionaryUtil.
You can now just call..
var count:int = DictionaryUtil.getKeyCount(myDictionary);

You can get keys of the Dictionary and check the length of keys array as below:
var d:Dictionary = new Dictionary();
d["a"] = "alpha";
d["b"] = "beta";
var count:int = DictionaryUtil.getKeys(d).length;

Related

Convert an Object's Name to a String

var variable:Object=new Object();
How would you convert "variable" as an object to "variable" as a string? I thought this would work:
var variable:Object=new Object();
var variable_string=String(variable);
You cannot get the name of a variable that holds an instance via said instance.
You could store the instance in an Object against a given key, which could be found using a for...in loop:
var myObject:Object = {};
var objects:Object = { variable: myObject };
for(var i:String in objects)
{
if(objects[i] === myObject)
{
trace(i); // variable
break;
}
}
All class level member names are stored in the string constant pool in your compiled SWF. Thus, it is in fact technically possible to get the name of variable by loading the bytes of the SWF and parsing them (or by using someone else's API to do it). However, this is probably more trouble than it's worth for whatever it is you're doing.

Effective algorithm for finding value objects in collection

Suppose we have some custom object type:
class SomeObjectType {
public var intProperty1:int;
public var intProperty2:int;
public var intProperty3:int;
public var stringProperty1:String;
public var stringProperty2:String;
public var stringProperty3:String;
public var stringPropertyThatActuallyIsInt1:String;
public var stringPropertyThatActuallyIsInt2:String;
public var stringPropertyThatActuallyIsInt3:String;
...
%ABOUT_20_ANOTHER_PROPERTIES_THAT_I_WON'T_USE%
}
We have a collection of more than 20k instances of these objects. And we have just 1 text input that is actually search filter. User can type in this filter field anything he want and if his filter matches with ANY of first 9 fields I described before we should leave this object in collection. Just simple items filtering.
And let me describe how it works in our project now. This algorithm casts all these properties to Strings, concatenate them, and search using indexOf() != -1 method. This is really slow. It takes about 500-900ms on my dev machine and about 3-4s on iPad on every filter change. Horrible statistics, isn't it?
Small note: we use this algorithm in different 3 places in app and objects differs from object I described above but idea is same. I believe that it is a good idea to compare int to int, string to string (implementing some of fast algorithms(there are lots if them)), and convert string that is actually to int and compare them as int to int, but object differs a lot so I need some common algorithm.
If by collection you mean ArrayCollection, I would recommend to use Vector instead.
Vectors are around 50 times faster then ArrayCollections.
If you need databinding, you could have a look at VectorCollection, but I can't imagine the performance to be anywhere close to Vector.
Also if you are not extending class SomeObjectType anywhere, you could gain some performance (especially on iOS) by making it final class SomeObjectType.
You can use dictionary to search, I think it will much faster, you just need to init for one time.
var dict:Dictionary = new Dictionary();
//get properties,in you obj, they are intProperty1,intProperty2 etc,exclude prototype
var properties:Array = ObjectUtil.getClassInfo(SomeObjectType, ["prototype"]).properties as Array;
for each (var obj:SomeObjectType in yourArrayCollection) {
for (var i:int = 0; i < properties.length; i++) {
var qname:Object = properties[i];
var k:String = qname.localName;
var v:String = obj[k];
if (dict[v] == null) {
dict[v] = [];
}
//add only one time
if ((dict[v] as Array).indexOf(obj) == -1) {
(dict[v] as Array).push(obj);
}
}
}
So if you want return all objs contains "5", just return dict[5]

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

How to clone a dictionary (and its content)?

I googled some time to find a way to clone a dic, and I found some options, but they didnt work. One was to use a byteArray, it works only if you register all the clases involved and your classes have all their constructor parameters with default values <- not a choice in my case.
Ohter was, a copy but only for the first values, this one:
private function clone(original:Dictionary):Dictionary {
var cloned:Dictionary = new Dictionary();
for(var key:Object in original) {
cloned[key] = original[key];
}
return cloned;
}
But, it doesnt clone a dicionary inside the main dictionary.
Do you know how to make a perfect clone of a given dictionary, even if it has another dictionary inside?
Thanks.
Make a recursive function that checks the elements type, if they are dictionaries themselves keep recursing and copying.
Something like (untested):
private function clone(original:Dictionary):Dictionary {
var cloned:Dictionary = new Dictionary();
for(var key:Object in original) {
if( original[key] is Dictionary)
cloned[key] = clone(original[key]);
else
cloned[key] = original[key];
}
return cloned;
}
Edit :
Seems to work for a simple nested dictionary like this :
var dic:Dictionary = new Dictionary();
dic["test"] = 5;
dic["otherDic"] = new Dictionary();
dic["otherDic"]["foo"] = 1;
dic["otherDic"]["bar"] = "bar";
var copy:Dictionary = clone(dic);
dic["test"] = 0;
dic["otherDic"]["foo"] = 0;
dic["otherDic"]["bar"] = "changed";
//copy doesn't change, copy and dic are different dictionaries

How can I copy an ArrayCollection of transfer objects in actionscript?

public function create():ArrayCollection{
var index:int = 0;
var data:ArrayCollection = new ArrayCollection();
var length:int = originalData.length;
for(index; index < length; index++){
data.addItem(originalData[index]);
}
return data;
}
originalData is the original state of my data from database.
data is a copy of originalData used to be manipulated as the provider for my List component.
There's a button I use to call the create() function above, that
would mean, I want to revert all changes in data, and go back to
everything I have in originalData.
But when I debug my function, originalData has all the changes made in data.
When I use
list.selectedItem.thing = "new string";
is supposed to modify data[index].thing, because data is my List.dataprovider. but it changes originalData[index].thing also and this collection wasn't used for anything, except for creating a copy of itself!
I don't know why this happens. I didn't know how to phrase this behaviour as a google query.
Please, if you don't understand the question, comment it so I can try and make it clearer. This has consumed more time than its functionality is worth.
EDIT:
I've also tried this, but it doesn't work:
public function create():ArrayCollection{
var index:int = 0;
var data:ArrayCollection = new ArrayCollection();
var length:int = originalData.length;
for(index; index < length; index++){
// initializing a Item object.
var dataItem:Item = new Item();
dataItem = originalData[index] as Item;
data.addItem(dataItem);
}
return data;
}
EDIT 2:
Based on your answers and some research I came up with this generic function to copy arrayCollections made of custom objects.
public static function copy(objectClassName:String, objectClass:Class, arrayCollection:ArrayCollection):ArrayCollection{
var index:int = 0;
var length:int = arrayCollection.length;
var copy:ArrayCollection = new ArrayCollection();
for(index; index < length; index++){
registerClassAlias(objectClassName,objectClass);
copy.addItemAt(ObjectUtil.copy(arrayCollection.getItemAt(index)) as objectClass,index);
}
return copy;
}
As Tom says, this is because AS3 passes by reference. If you don't want to modify the original values then you should, again as Tom says, create copies of them.
Fortunately, AS3 has a utility to do this -- ObjectUtils.copy. Try this code instead of your original:
public function create():ArrayCollection{
var index:int = 0;
var data:ArrayCollection = new ArrayCollection();
var length:int = originalData.length;
for(index; index < length; index++){
data.addItem(mx.utils.ObjectUtil.copy(originalData[index]));
}
return data;
}
Do take note that copy() returns a generic Object. If you want to access any of its properties in a type-safe manner you'll have to cast it to its type.
A bit more about the pass-by-reference deal. Let's say we have items a, b, and c floating around in memory. You put them into an array(originalData). originalData now contains references to a, b, and c. You then create an ArrayCollection and insert (again) references to a, b, c. Objects a, b, and c aren't what's being stored in either the array or the ArrayCollection. So when you update originalData[0] you're getting a reference to an object (a), and updating it.
Likewise when you update ArrayCollection.getItemAt(0) you're getting the same reference and updating the underlying object which is why you're getting the behavior you're getting. Making a copy and inserting it means you're referencing an entirely new object.
I am not used to actionscript, but it looks to me like you are putting references to objects from one arrayCollection to another arrayCollection. So if you change those objects, this will be reflected in both arrayCollections since they refer to the same objects.
To avoid this you should make copies from the original objects to put in the new arrayCollection.
To create a deep copy of an array and keep the type, use the following method:
public static function clone(source:Object):*
{
var myBA:ByteArray = new ByteArray();
myBA.writeObject(source);
myBA.position = 0;
return(myBA.readObject());
}
This is the way proposed by Adobe.
Update: idea for a type safe copy function
public static function copyTypeSafe( ac:ArrayCollection ):ArrayCollection
{
var cloneAc:ArrayCollection = new ArrayCollection();
if( ac.length == 0 ) {
return cloneAc;
}
var className:String = getQualifiedClassName( ac.getItemAt(0) );
registerClassAlias( className, (getDefinitionByName( className ) as Class ) );
for each (var obj:Object in ac)
{
cloneAc.addItem( ObjectUtil.copy( obj ) );
}
return cloneAc;
}
You need to do a registerClassAlias call for each of the classes inside of your collection, then just use the function clone with the ByteArray suggested above and it will work fine. The key is to do a registerClassAlias call for each type inside of the ArrayCollection which you want to maintain. In my case:
var productGroupClassName:String = getQualifiedClassName(ProductGroup);
registerClassAlias( productGroupClassName, ProductGroup );
var productClassName:String = getQualifiedClassName(Product);
registerClassAlias( productClassName, Product );
//Need to create a copy so the original values will not be altered
dataProvider = copyTypeSafe(EnalityData.productsInfo);