I'm trying to workout the fastest way to match two dictionaries in Actionscript-3. This is what I've got so far.
function compareDictionaries(p0:Dictionary, p1:Dictionary):Boolean
{
if(p0 == p1) {
return true;
} else {
const matched:Dictionary = new Dictionary();
for(var k0:Object in p0) {
matched[k0] = k0;
if(p0[k0] != p1[k0]) {
return false;
}
}
for(var k1:Object in p1) {
if(matched[k1]) {
continue;
} else {
if(p1[k1] != p0[k1]) {
return false;
}
}
}
return true;
}
}
Obviously it's not ideal to create a new dictionary with the key for the item, but I really don't want to retest the matched item (not that I know if this is any slower or not!). This of course would be circumvented by having a length on the dictionary class, which would make the second for loop redundant.
Any better ideas over this?
EDIT
I created a gist of the benchmark to show the results for a successful match (use the release player)
(results output in (ms))
true
43
true
497
true
22
true
16
EDIT 2
I created another gist showing a miss-hit of equality.
(results output in (ms))
false
1
false
472
false
0
false
0
Something simple like this should work - no new arrays/dictionaries etc:
public function compareDictionaries( d1:Dictionary, d2:Dictionary ):Boolean
{
// quick check for the same object
if( d1 == d2 )
return true;
// check for null
if( d1 == null || d2 == null )
return false;
// go through the keys in d1 and check if they're in d2 - also keep a count
var count:int = 0;
for( var key:* in d1 )
{
// check if the key exists
if( !( key in d2 ) )
return false;
// check that the values are the same
if( d1[key] != d2[key] )
return false;
count++;
}
// now just make sure d2 has the same number of keys
var count2:int = 0;
for( key in d2 )
count2++;
// return if they're the same size
return ( count == count2 );
}
Technically you could just make a direct comparison with the values (as in don't check for the existence of the key in d2) as if you search for a key that's not there, it should resolve to null (or undefined, I'm not 100% sure), but I left it in there in the case where a value in d1 is null and it doesn't exist in d2
Start by comparing strict equality. Then, test equality of the keys in both dictionaries:
Iterate over the properties and return all keys in arrays
Test for equal array length
Sort the arrays
Iterate once more for comparison
Only if all of those tests are passed, compare the actual values:
public function areEqualDictionaries( dict1:Dictionary, dict2:Dictionary ):Boolean {
if(dict1 === dict2) return true;
var keys:Array = equalKeysArrayOrNull( dict1, dict2 );
if(keys)
return haveEqualValues( keys, dict1, dict2 );
else
return false;
}
private function equalKeysArrayOrNull( dict1:Dictionary, dict2:Dictionary ):Array {
var keys1:Array = enumerateKeys( dict1 ).sort();
var keys2:Array = enumerateKeys( dict2 ).sort();
if( keys1.length != keys2.length ) return null;
var i:int = -1;
while(++i < keys1.length)
if(keys1[i] !== keys2[i]) return null;
return keys1;
}
private function haveEqualValues ( keys:Array, dict1 : Dictionary, dict2:Dictionary) :Boolean {
for each (var key:* in keys)
if (dict1[key] != dict2[key]) return false;
return true;
}
private function enumerateKeys( dict:Dictionary ):Array {
var keys:Array = [];
for(var key:* in dict)
keys.push( key );
return keys;
}
Note that dictionaries use identity (strict equality) for matching keys, which makes it necessary to use !== for comparing them. I suppose it's okay to use != for comparing the values, though.
EDIT: Comparison of for vs. for each
Here's my own benchmark for vs. for each:
var dict : Dictionary = new Dictionary();
var keys : Array = [];
for (var i : int = 0; i < 1000000; i++) {
var n : Object = { index:i };
dict[n] = n;
keys.push (n);
}
trace ("benchmark:");
trace ("----------");
var start : int = getTimer();
for each (var key:* in keys) {
var m:* = dict[key];
}
var elapsed : int = getTimer() - start;
trace ("for each:" + elapsed);
trace ("----------");
start = getTimer();
for (var j:int = 0; j < keys.length; j++) {
var o:* = dict[keys[j]];
}
elapsed = getTimer() - start;
trace ("for:" + elapsed);
returns on my machine:
for each:213
----------
for:274
Related
function ShiftCalcData(theEventId, num)
{
this.event = theEventId;
this.numOptions = num;
this.shiftDone = false;
if ( this.numOptions == 0 )
this.shiftDone = true;
return this;
}
This is a "class ShiftCalcData" defined.
I declared
var shiftData = [];
and later I add objects to the list
shiftData.push( new ShiftCalcData(eventId, count) );
and I have a function
function getNextShift(numCols)
{
var min = 100;
var selCol = 1;
for ( var j = 2; j < numCols ; j++ )
{
var eq = shiftData.length == numCols;
var tmp = shiftData[j];
if ( tmp.shiftDone == false )
{
if ( tmp.numOptions < min )
{
selCol = j;
min = tmp.numOptions;
}
}
}
return selCol;
}
And I get the error "TypeError: Cannot read property "shiftDone" from undefined. "
When I look at the debugger, I am sure to see 'shiftDone' properties
I know the code looks "very simple" but I tried too many things...
I am new to writing Google Apps Scripts...
Please
var a:Object =({label:"2008",n:8560,i:15909});
var b:Object ={(label:"2009",n:8146,i:14197});
Lets say I have five objects similar to this in a list component. Is there possible to have a function that prints out the label of the item which has the lowest value n ? What would I have to do to accomplish this?
Sort on the field you want first (n in this case) using Array.sortOn(), then access it and print:
var arrayToSort:Array = [{label:"2008",n:8560,i:15909},{label:"2009",n:8146,i:14197}];
var sortedArray = arrayToSort.sortOn ("n" , Array.NUMERIC);
trace(sortedArray[0].label);
public function findMax( o:Object ):String {
var lowest:Number = Number.MAX_VALUE;
var lowestLabel:String;
for (var label:String in o) {
var val:Number = o[label];
if ( val != null && val < lowest ) {
lowest = val;
lowestLabel = label;
}
}
return lowestLabel;
}
please inspect me coding:
function createRandomList():void
{
var newlist:Array = [0,1,2];
var curlist:Array = item[selectedlevel - 1] //selectedlevel = 1;
var normal:int = curlist[0];
var tempboo1:Boolean = false;
var tempboo2:Boolean = false;
var tempboo3:Boolean = false;
while (curlist[0] + curlist[1] + curlist[2] > 0)
{
if (Number(curlist[0]) == 0 && tempboo1 == false)
{
newlist.splice(newlist.indexOf(0), 1);
tempboo1 = true;
}
if (Number(curlist[1]) == 0 && tempboo2 == false)
{
newlist.splice(newlist.indexOf(1), 1);
tempboo2 = true;
}
if (Number(curlist[2]) == 0 && tempboo3 == false)
{
newlist.splice(newlist.indexOf(2), 1);
tempboo3 = true;
}
var temp:int = Math.floor(Math.random()*(newlist.length));
curlist[temp] -= 1;
generatedlist.push(Number(newlist[temp]));
trace(item);
}
while (normal > 0)
{
var temp2:int = Math.floor(Math.random() * 3) + 1;
generatednormal.push(Number(temp2));
normal--;
}
}
My item was [[5,0,0],[10,0,0]];
But after became [[0,0,0],[0,0,0]];
I just want to duplicate Array item to be a new variable curlist.
Every time it traces, returning item[0][0] decreasing 1, I only want to use curlist as a temp Array to calculate a new random Array based on item[0].
Ouput:
4,0,0,10,0,0
3,0,0,10,0,0
2,0,0,10,0,0
1,0,0,10,0,0
0,0,0,10,0,0
Is there any links between them, or is it my problem? Please help! If you need any more infoemation, please comment me!
Arrays are passed by reference, not value. That means when you modify an array through any property that points to it, the original array will be modified.
To make a duplicate, you can use .slice()
Returns a new array that consists of a range of elements from the original array, without modifying the original array. The returned array includes the startIndex element and all elements up to, but not including, the endIndex element.
If you don't pass any parameters, the new array is a duplicate (shallow clone) of the original array.
You can clone your arrays if you want to create a new reference.
function clone( source:Object ):*
{
var myBA:ByteArray = new ByteArray();
myBA.writeObject( source );
myBA.position = 0;
return( myBA.readObject() );
}
var a:Array = [[0,0],[1,1]];
var b:Array = clone(a);
b[0] = [2,2];
trace(a)
trace(b)
Output
0,0,1,1
2,2,1,1
It works for any object, not only arrays.
More infos here : AS3 - Clone an object
var array : Array = [ 1, 2, 3];
var array2 : Array = array.concnt();
array[ 0 ] = 4;
trace( array );// 1, 2, 3
trace( array 2);// 4, 2 ,3
So use .concat() to duplicate an array with primitives. If you have an arrays with arrays. Duplicate the children arrays, and put them into an empty one. If you have children of children arrays and so forth, make something recursive.
From what I understand:
If I want to hittest two non-array objects, the code would be:
if(player.hitTestObject(enemy))
{
}
The same code would also work if I was testing a non-array object with every object in an array.
But what do I do if I want to hittest every object in one array with every object in a second array?
Here's a resource-friendly one, if all you want to know is whether ANY of the items hit ANY other item:
public function anyItemWasHit( arr1:Array, arr2:Array ) : Boolean {
for each( var item:DisplayObject in arr1)
if( itemHitArrItem( item, arr2 ) )
return true;
return false;
}
private function itemHitArrItem( item:DisplayObject, arr:Array ) : Boolean {
for each( var arrItem:DisplayObject in arr )
if( item.hitTestObject( arrItem ) )
return true;
return false;
}
Based on the previous example, here's one that returns an array of ALL items that hit ANY other item:
public function itemsThatHitOtherItems( arr1:Array, arr2:Array ) : Array {
var items:Array = [];
for each( var item:DisplayObject in arr1 )
if( itemHitArrItem( item, arr2) )
items[items.length] = item;
return items;
}
And, finally, one that returns an array of all pairs (the item that hit another item, AND the item that was hit):
public function allPairsThatHitEachOther( arr1:Array, arr2:Array ) : Array {
var pairs:Array = [];
for each( var item:DisplayObject in arr1 ) {
var itemPairs:Array = allPairsForItem( item, arr2 );
if ( itemPairs.length > 0 )
pairs = pairs.concat( itemPairs );
}
return pairs;
}
private function allPairsForItem( item:DisplayObject, array:Array ) : Array {
var pairs:Array = [];
for each( var otherItem:DisplayObject in array )
if( item.hitTestObject( otherItem ) )
pairs[pairs.length] = [item, otherItem];
return pairs;
}
I'm guessing you're really just asking about the syntax. In this case it would be:
where:
indexOne goes from 0 to arrayOne.length - 1 (can be in a for loop)
indexTwo goes from 0 to arrayTwo.length - 1 (can also be in a nested for loop)
arrayOne[indexOne].hitTestObject(arrayTwo[indexTwo])
outerLoop: for (var i:int = 0; i < arr1.length; i++) {
var obj1:DisplayObject = arr1[i] as DisplayObject;
for (var j:int = 0; j < arr2.length; j++) {
var obj2:DisplayObject = arr2[j] as DisplayObject;
if (obj1.hitTestObject(obj2)) {
// these 2 hit each other
break outerLoop;
}
}
}
How can you trace the "depth" or stacking order of a display object with AS3?
I'm trying to figure out if my sprite is behind another sprite...
container.getChildIndex(displayObject);
but that will only tell you how deep it is, not necessarily if anything is in front of it.
Function comparing two DisplayObject instances to determine which one is at a higher "depth" on the display list:
private function higher(a:DisplayObject, b:DisplayObject):DisplayObject
{
// Parent chains
var ac:Array = [a];
var bc:Array = [b];
// Pointers to individual nodes
var an:DisplayObject = a.parent;
var bn:DisplayObject = b.parent;
while (an != null) {
ac.push(an);
an = an.parent;
}
while (bn != null) {
bc.push(bn);
bn = bn.parent;
}
var acl:int = ac.length;
var bcl:int = bc.length;
var n:int = Math.min(acl, bcl);
var i:int = 0;
for (; i < n; i++) {
an = ac[acl - i - 1];
bn = bc[bcl - i - 1];
// First uncommon ancestor
if (an != bn)
break;
}
var ca:DisplayObjectContainer = an.parent;
if (!ca)
return null;
if (ca.getChildIndex(an) > ca.getChildIndex(bn))
return a;
else
return b;
}
Note: If one of the objects is not on the display list, the function returns null. You can change it to return the other object instead.
You can almost certainly optimize this, but this is a first cut.
Just a refactored version of Manish answer using vectors and which won't return weird result if you ever call higher(a,a.parent).
parents() may be used for other purpose too :
public function higher(a:DisplayObject, b:DisplayObject):DisplayObject
{
var aParents:Vector.<DisplayObject> = parents(a);
var bParents:Vector.<DisplayObject> = parents(b);
var commonDepth:int = Math.min(aParents.length, bParents.length);
for (var depth:int = 0; depth < commonDepth; depth++)
if (aParents[depth] != bParents[depth])
break;
if (depth == 0 || depth == commonDepth)
return null;
var commonAncestor:DisplayObjectContainer = aParents[depth].parent;
if (commonAncestor.getChildIndex(aParents[depth]) > commonAncestor.getChildIndex(bParents[depth]))
return a;
else
return b;
}
private function parents(d:DisplayObject):Vector.<DisplayObject>
{
var result:Vector.<DisplayObject> = new Vector.<DisplayObject>;
while (d != null)
{
result.unshift(d);
d = d.parent;
}
return result;
}
private function getDepth(clip:DisplayObject):uint
{
var depth:uint = 0;
var currentClip:DisplayObject = clip;
while (currentClip.parent && currentClip.parent != this)
{
depth++;
currentClip = currentClip.parent;
}
return depth;
}
container.getChildIndex(child) should do it, it returns the index of the child
This is a revised version of what jauboux did from a version Manish did.
Namely, adding a null return value from highestOf() when depths match.
/**
* #param ifDepthsMatchReturnObjectA
* #return Whichever DisplayObject is higher on the display list.
* Optionally returns `null` if they're at the same depth.
*/
public function highestOf(a:DisplayObject, b:DisplayObject, ifDepthsMatchReturnObjectA:Boolean = false):DisplayObject
{
var aParents:Vector.<DisplayObject> = ancestorsOf(a);
var bParents:Vector.<DisplayObject> = ancestorsOf(b);
var commonDepth:int = Math.min(aParents.length, bParents.length);
for (var depth:int = 0; depth < commonDepth; depth++)
if (aParents[depth] != bParents[depth])
break;
if (depth == 0 || depth == commonDepth)
return null;
var commonAncestor:DisplayObjectContainer = aParents[depth].parent;
var aDepthOnCommonAncestor:int = commonAncestor.getChildIndex(aParents[depth]);
var bDepthOnCommonAncestor:int = commonAncestor.getChildIndex(bParents[depth]);
if (aDepthOnCommonAncestor > bDepthOnCommonAncestor)
return a;
else if (aDepthOnCommonAncestor < bDepthOnCommonAncestor)
return b;
else
return ifDepthsMatchReturnObjectA ? a : null;
}
/**
* #return Whether a is higher than b.
*/
public function isHigher(a:DisplayObject, b:DisplayObject):Boolean
{
return highestOf(a, b) === a;
}
/**
* #return All ancestors of given display.
*/
private function ancestorsOf(display:DisplayObject):Vector.<DisplayObject>
{
var result:Vector.<DisplayObject> = new Vector.<DisplayObject>;
while (display != null)
{
result.unshift(display);
display = display.parent;
}
return result;
}