Large Numbers as keys in Dictionaries (ActionScript 3) - actionscript-3

I am using a Dictionary where the keys are Number objects, but I run into unexpected problems if the keys are large integers.
Here is some example code that inserts two key-value pairs in a Dictionary, one with a small key and one with a large one:
var dictionary:Dictionary = new Dictionary();
var smallNumberKey:Number = 1;
dictionary[smallNumberKey] = "some value";
var largeNumberKey:Number = 0x10000000;
dictionary[largeNumberKey] = "some value";
for (var key:Object in dictionary) {
trace("Key: " + key);
trace("Key type: " + flash.utils.getQualifiedClassName(key));
trace("Key as Number: " + (key as Number));
}
This gives the following output:
Key: 1
Key type: int
Key as Number: 1
Key: 268435456
Key type: String
Key as Number: null
None of the keys seems to be stored as a Number. Why is that? The first one is stored as an int, which can be converted to a Number. The second one however seems to be stored as a String, which makes no sense to me. What is going on here?

You can't use a Number as a key, because binary floating-point numbers do not lend themselves well to exact comparison, which is exactly what the Dictionary class does. (is "uses strict equality (===) for key comparison"). Read up on floating point numbers to understand why.
So when you assign a Number as a key to a Dictionary, the Flash engine must convert it to some other thing which can be represented exactly. String and int values can both be represented exactly by their contents, so Flash picks one to convert the supplied Number to. Doing some experimenting, it seems flash always converts an whole number value (even if supplied as a string) to an int if less than or equal to 0xFFFFFFF, and always converts to a String any whole number greater than that, probably due to some internal optimization in the fastest way to compare keys:
import flash.utils.Dictionary;
import flash.utils.getQualifiedClassName;
var dictionary:Dictionary = new Dictionary();
var stringKey:String = "123456789";
var intKey:int = 0xFFFFFFF;
var intKey2:int = intKey + 1;
dictionary[stringKey] = "Value 1";
dictionary[intKey] = "Value 2";
dictionary[intKey2] = "Value 3";
for (var key:Object in dictionary) {
trace( key + " [" + getQualifiedClassName(key) + "] = " + dictionary[key]);
}
Prints:
123456789 [int] = Value 1
268435455 [int] = Value 2
268435456 [String] = Value 3

From the Adobe doc:
The Dictionary class lets you create a dynamic collection of properties, which uses strict equality (===) for key comparison. When an object is used as a key, the object's identity is used to look up the object, and not the value returned from calling toString() on it.
"Using the object's identity" means using a reference to the object, a pointer to the place in memory where the actual object is stored. The problem when using primitive values as dictionary keys is that they are never passed by reference, and thus the values are not stored in the actual dictionary (i.e. they are not references to an object), but rather treated as "regular" dynamic properties, just like the ones you would expect from an Object. When you set
object[key] = "some value";
the key values are automatically converted either to int(just like the index of an Array) or String.
You can get around this, and use the actual performance benefits of Dictionary, by using a simple NumberKey wrapper class:
package
{
import flash.display.Sprite;
import flash.utils.Dictionary;
public class SimpleTest extends Sprite
{
var dictionary:Dictionary = new Dictionary();
public function SimpleTest() {
var smallNumberKey:NumberKey = new NumberKey(1);
dictionary[smallNumberKey] = "some value";
var largeNumberKey:NumberKey = new NumberKey(0x10000000);
dictionary[largeNumberKey] = "some value";
for(var key:Object in dictionary) {
trace( "Key: "+key );
trace( "Key type: "+flash.utils.getQualifiedClassName( key ) );
trace( "Key as Number: "+(key.numberValue) );
}
}
}
}
package {
public class NumberKey {
public var numberValue : Number;
public function NumberKey( n:Number ) {
numberValue = n;
}
}
}

Related

Issues with a UDF

I have a UDF that accepts a bag as input and converts it to a map. Each key of the map consists of the distinct elements in the bag and the values corresponding to their count
But it's failing the junit tests
It seems you except to use a bag of two tuples, but you are indeed creating a bag that contains a tuple with two fields.
This code :
DataBag dataBag = bagFactory.newDefaultBag();
Tuple nestedTuple = tupleFactory.newTuple(2);
nestedTuple.set(0, "12345");
nestedTuple.set(1, "12345");
dataBag.add(nestedTuple);
should be transformed to :
DataBag dataBag = bagFactory.newDefaultBag();
Tuple tupleA = tupleFactory.newTuple(1);
tupleA.set(0, "12345");
dataBag.add(tupleA);
Tuple tupleB = tupleFactory.newTuple(1);
tupleB.set(0, "12345");
dataBag.add(tupleB);
Or you may iterate on all your tuple's fields.
The output of 1 is correct: in your UDF you are counting the number of tuples that have the same value for the first field, but in the test you are adding only one tuple with two values.
If what you want is to count the number of tuples with the same value as "key" (where key is the first value in your tuple), then what you are doing is correct, but you would have to change your test:
public void testExecWithSimpleMap() throws Exception {
Tuple inputTuple = tupleFactory.newTuple(1);
DataBag dataBag = bagFactory.newDefaultBag();
Tuple nestedTuple = tupleFactory.newTuple(2);
nestedTuple.set(0, "12345");
nestedTuple.set(1, "another value");
dataBag.add(nestedTuple);
// Add a second tuple
nestedTuple.set(0, "12345");
nestedTuple.set(1, "and another value");
dataBag.add(nestedTuple);
inputTuple.set(0,dataBag);
Map output = testClass.exec(inputTuple);
assertEquals(output.size(), 1);
System.out.println(output.get("12345"));
assertEquals(output.get("12345"),2);
}
However, if what you wanted was to count how many times a value is repeated in the whole Bag, whether it's on different Tuples or on the same Tuple, (it is not very clear in your question), then you need to change your UDF:
public class BagToMap extends EvalFunc<Map> {
public Map exec(Tuple input) throws IOException {
if(input == null) {
return null;
}
DataBag values = (DataBag)input.get(0);
Map<String, Integer> m = new HashMap<String, Integer>();
for (Iterator<Tuple> it = values.iterator(); it.hasNext();) {
Tuple t = it.next();
// Iterate through the Tuple as well
for (Iterator<Object> it2 = t.iterator(); it2.hasNext();) {
Object o = it2.next();
String key = o.toString();
if(m.containsKey(key)) {
m.put(key, m.get(key)+1);
} else {
m.put(key, 1);
}
}
}
return m;
}
}
In this case, your test should pass.

Object's unique integer or string identifier (hash)

Is there a method or other native way to get or store a unique identifier for an object? (perhaps with haxe native access or the actionscript API).
The reason is to facilitate contemplation about the Dictionary and other datastructures that operate on the uniqueness of an object index.
If your are using Dictionaries, the object itself could be used as key, so there you have your uniqueness.
var dict:Dictionary = new Dictionary();
var obj:SomeClass = new SomeClass();
dict[obj] = "whatever";
For other data structures, you could try to generate a sequential number statically. In many cases, this should be enough, I think.
Something like:
class UniqueKey {
private static var _key:int = 0;
public static function getNextKey():int {
return ++_key;
}
}
And to use it:
var obj:SomeClass = new SomeClass();
obj.unique = UniqueKey.getNextKey();

AS3 XMLListCollection data sorting on multiple fields and crashing when value changed

My XML:
<destinations>
<destination>
<fav>1</fav>
<cheapest>140</cheapest>
</destination>
<destination>
<fav>0</fav>
<cheapest>150</cheapest>
</destination>
</destinations>
I am creating XMLListCollection for my spark List component.
var dataprovider:XMLListCollection = new XMLListCollection(xml.destination);
I am trying to sort this XMLListCollection using fav and cheapest element.
var sort:Sort = new Sort();
sort.fields = [new SortField("fav" , true , true)];
sort.fields.push(new SortField("cheapest" , false , true));
dataprovider.sort = sort;
dataprovider.refresh();
Everything works fine till I update value of fav:
xml.destination.(id == String(destId))[0].fav = 0;
XML structure looks exactly the same after the update but I get thrown error from my itemrenderer object :
override public function set data( value:Object ) : void {
dest_name.text = value.text;
}
Error stating that value is null. How can value be null in the first place? I get no error when I remove fav from sort fields or update cheapest element instead.
Does anyone have any idea about this anomaly?
You have to take into account that your itemrenderers are recycled, for example, if order of items in your collection changes(when you change value of sort field). When renderers are recycled, null can be passed to set data function.
That means your function
override public function set data( value:Object ) : void {
dest_name.text = value.text;
}
shall be changed like that:
override public function set data( value:Object ) : void {
if(value){
dest_name.text = value.text;
}
}
You should always keep this in mind when implementing item renderers.

flex dictionary bug?

I was testing a HashMap implementation in AS3 .
I have tried the following code:
var map:IMap = new HashMap();
map.put("a", "value A");
map.put("b", "value B");
map.put("c", "value C");
map.put("x", "value X");
map.put("y", "value Y");
map.put("z", "value Z");
Then I called the clear() method:
map.clear();
The size of the hashmap didn't become 0, but it was 1. The problem is that when the key is "y", it doesn't get removed.
The corresponding code is as follows:
protected var map:Dictionary = null;
public function HashMap(useWeakReferences:Boolean = true)
{
map = new Dictionary( useWeakReferences );
}
public function put(key:*, value:*) : void
{
map[key] = value;
}
public function remove(key:*) : void
{
map[ key ] = undefined;
delete map[ key ];
}
public function clear() : void
{
for ( var key:* in map )
{
remove( key );
}
}
If I call the clear() function again, the remaining key will be removed:
if (size() != 0)
{
clear();
}
Does anyone know what's the reason the y key doesn't get deleted?
I haven't had the time to look at the Dictionary implementation in tamarin (the VM for flash), but it seems that the Dictionary is beeing rehashed when a value is reaffected to the map by the line map[ key ] = undefined; in the remove function.
I.E. you begin an iteration with a set of key but then a rehashed occured and the keys are not valid anymore and the VM can't find the previous key and so in this case miss the y key.
What you can do is remove the map[key] = undefined; from the remove function and it should work. What it's strange is that the delete didn't produce any similar bug...
To show that the rehash has occurred see here a live example :
http://wonderfl.net/c/2PZT
You will see that a key is iterate twice when you assign a value to the dictionary.

AS3: Optimizing Object Memory Size

I have have a class that I wrote, and it seems bigger than it should be. It doesn't extend anything, and has very little going on - or so I thought - but each one is taking up just under 100k100 bytes ( thanks back2dos ). I guess that I don't have a very good understanding of what really affects how much memory an object takes up in AS3.
If anyone can point me to some reading on the subject that might be helpful, or perhaps explain some insight into how to think about this, that would be awesome.
I would like to keep a LOT of these objects in memory - and I thought I could until now, but at this size I'm going to have to create them or use an object pooling technique of some kind.
Thanks for the assistance.
Edit: Although I've got this in order, I'm keeping the code I posted here for completeness. The class has been heavily modified from the original version. Values that were referencing other files have been made static as to allow the code to run for someone else ( in theory hehehe... ).
Although my situation is sorted out, I'll give the answer to a good reference for information on classes and memory.
In this case the class has 15 variables. I'm only using a single String and a bunch of ints, Numbers, and Booleans with some references to more of the same in globally available XML data. It also imports Point for the constructor, though no points are stored. In testing, even without the global XML references or Point class it's still around a ~84k each. There are getters for 7 of the variables and a couple methods in addition to the constructor. All of which are less than 20 lines ( and I have a very sparse coding style ).
The class mentioned for reference, but feel free to generalize:
package
{
public class AObject
{
private var _counter:int;
private var _frames:int;
private var _speed:int;
private var _currentState:String;
private var _currentFrame:int;
private var _offset:int;
private var _endFrame:int;
private var _type:int;
private var _object:int;
private var _state:int;
private var _x:Number;
private var _y:Number;
private var _w:int;
private var _h:int;
private var _update:Boolean;
public function AObject( targetX : int, targetY : int, state : int, object : int, type : int )
{
_x = targetX;
_y = targetY;
_type = type;
_object = object;
_state = state;
_counter = 0;
_w = 32;
_h = 32
_update = true;
setState( state );
}
public function setState( state:int ) : void
{
_currentState = "bob";
var frameCounter : int = 0;
var stateCounter : int = state - 1;
while ( state > 0 )
{
frameCounter += 4;
--stateCounter;
}
_offset = frameCounter;
_currentFrame = _offset;
_speed = 10;
_frames = 4;
_endFrame = _offset + _frames - 1;
}
public function get state() : int
{
return _state;
}
public function animate() : Boolean
{
if ( count() )
{
if( _currentFrame < _endFrame )
{
++_currentFrame;
}
else
{
_currentFrame = _offset;
}
_speed = 10;
return true;
}
else
{
return false;
}
}
private var adder: Number = 0;
private function count():Boolean
{
_counter++;
if ( _counter == _speed )
{
_counter = 0;
return true;
}
else
{
return false;
}
}
public function get x():int
{
return _x;
}
public function get y():int
{
return _y;
}
public function get type():int
{
return _type;
}
public function get object():int
{
return _object;
}
public function get currentFrame():int
{
return _currentFrame;
}
public function get w():int
{
return _w;
}
public function get h():int
{
return _h;
}
}
}
i am amazed, this compiles at all ... when i try to compile it with the flex SDK, it creates an enormous collision with the built-in class Object, which is the base class of any class, making my trace output overflow ...
other than that, this is an infinite loop if you pass a value for state bigger than 0
while ( state > 0 )
{
frameCounter += 4;
--stateCounter;
}
but it seems really strange these objects are so big ... after renaming and taking care not to pass in 0 for the state, i ran a test:
package {
import flash.display.Sprite;
import flash.sampler.getSize;
import flash.system.System;
public class Main extends Sprite {
public function Main():void {
const count:int = 100000;
var start:uint = System.totalMemory;
var a:Array = [];
for (var i:int = 0; i < count; i++) {
a.push(new MyObject(1, 2, 0, 4, 5));
}
var mem:uint = System.totalMemory - start - getSize(a);
trace("total of "+mem+" B for "+count+" objects, aprox. avg. size per object: "+(mem/count));
}
}
}
it yields:
total of 10982744 B for 100000 objects, aprox. avg. size per object: 109.82744
so that's quite ok ... i think the actual size should be 4 (for the bool) + 4 * 11 (for the ints) + 4 (for the reference to the string) + 8 * 3 (for the three floats (you have the adder somewhere over the count) + 8 for an empty class (reference to the traits objects + something else), giving you a total of 88 bytes ... which is, what you get, if you getSize the object ... please note however, that getSize will only give you the size of the object itself (as calculated here) ignoring the size of what strings or other objects your object references ...
so yeah, apart from that name you definitely should change, the problem must be somewhere else ...
greetz
back2dos
If you really want to save on space, you can fake shorts by using unsigned integers, and using upper/lower bits for one thing or another.
ints are 4 bytes by nature, you can reuse that int on anything less than 2^8.
width height
0xFFFF + 0xFFFF
offset endframe
0xFFFF + 0xFFFF
This though gets ugly when you want to write anything or read anything, as to write width or height you'd have to:
writing:
size = (width & 0x0000FFFF) << 16 | (height & 0x0000FFFF);
reading:
get width():uint { return (size & 0xFFFF0000) >> 16 };
That's ugly. Since you're using getters anyways, and assuming computation speed is not an issue, you could use internal byte arrays which could give you even more granularity for how you want to store your information. Assuming your strings are more than 4 bytes, makes more sense to use a number rather than a string.
Also, I believe you will actually get some memory increase by declaring the class as final, as I believe final functions get placed into the traits object, rather than