ActionScript - Comparing & Removing Duplicates of Complex Arrays? - actionscript-3

when comparing simple arrays, i use something like the following function to concatenate and remove duplicates:
//Merge
public function merge(a1:Array, a2:Array):Array
{
var result:Array = a1.concat(a2);
var dictionary:Dictionary = new Dictionary();
for each (var item:Object in result)
dictionary[item] = true;
result = new Array();
for (var key:Object in dictionary)
result.push(key);
dictionary = null;
return result;
}
however, this approach doesn't work on complex arrays.
is there a well known algorithm, or is it even possible to write a function of recursion that can compare a Vector.<Object> with another? one that will always work even if the some objects being compared have additional key/value pairs?
[EDIT]
to be more clear, using a dictionary to determine if items in a simple array only works on primitive data types (int, number, string, etc.) or object references, so the above example works if it's passed 2 arrays resembling something like this:
var arr1:Array = new Array(1, 2, 3, 4, 5);
var arr2:Array = new Array(8, 7, 6, 5, 4);
resulting in a merged array with the following values:
1, 2, 3, 8, 7, 6, 5, 4
in contrast, i'm asking if it's possible to pass a function 2 complex arrays or Vector.<Object> all containing unique objects that may have identical key/value pairs and remove redundencies in the resulting Vector.<Object>. for example:
var vec1:Vector.<Object> = new Vector.<Object>();
vec1.push({city:"Montreal", country:"Canada"});
vec1.push({city:"Halifax", country:"Canada"});
var vec2:Vector.<Object> = new Vector.<Object>();
vec2.push({city:"Halifax", country:"Canada"});
vec2.push({city:"Toronto", country:"Canada"});
merging the above 2 vector objects would result in the following vector by determining and removing objects with identical key/value pairs:
{city:"Montreal", country:"Canada"}
{city:"Halifax", country:"Canada"}
{city:"Toronto", country:"Canada"}
i'm searching for an algorithm which could handle the removal of these similar objects without having to know about their specific key/value names or how many key/value pairs there are within the object.

Sure you can, you can build a similar example with any type of Vector:
public function mergeObjectVectors(v1:Vector.<Object>,
v2:Vector.<Object>):Vector.<Object>
{
var dictionary:Dictionary = new Dictionary();
var concat:Vector.<Object> = v1.concat(v2);
var result:Vector.<Object> = new Vector.<Object>();
for each(var i:Object in concat)
{
if (!dictionary[i])
{
dictionary[i] = true;
result.push(i);
}
}
return result;
}
However if you plan on accepting vectors of any type, it's different:
public function testing():void
{
var v1:Vector.<Object> = new Vector.<Object>();
v1.push({name:"Object 1"});
v1.push({name:"Object 2"});
// Vector w duplicates
var v2:Vector.<Object> = new Vector.<Object>();
var o:Object = {name:"Object 3"};
v2.push(o);
v2.push(o);
v2.push(o);
var resultVector:Vector.<Object> = mergeAnything(v1, v2, Class(Vector.<Object>));
var resultArray:Array = mergeAnything(v1, v2, Array);
var resultObject:Object = mergeAnything(v1, v2, Object);
}
public function mergeAnything(o1:Object, o2:Object, resultClass:Class):*
{
var dictionary:Dictionary = new Dictionary();
var result:Object = new resultClass();
var i:int;
for each(var o:Object in o1)
{
if (!dictionary[o])
{
dictionary[o] = true;
result[i++] = o;
}
}
for each(o in o2)
{
if (!dictionary[o])
{
dictionary[o] = true;
result[i++] = o;
}
}
return result;
}
The first example will be more resource-efficient.
EDIT:
This should do it, try it with your example:
public function mergeObjectVectors(v1:Vector.<Object>, v2:Vector.<Object>):Vector.<Object>
{
var concat:Vector.<Object> = v1.concat(v2);
var result:Vector.<Object> = new Vector.<Object>();
var n:int = concat.length;
loop:for (var i:int = 0; i < n; i++)
{
var objectToAdd:Object = concat[i];
var m:int = result.length;
for (var j:int = 0; j < m; j++)
{
var addedObject:Object = result[j];
if (this.areObjectsIdentical(objectToAdd, addedObject))
{
continue loop;
}
}
result.push(objectToAdd);
}
return result;
}
private function areObjectsIdentical(o1:Object, o2:Object):Boolean
{
var numComparisons:int = 0;
for (var s:String in o1)
{
numComparisons++;
if (o1[s] != o2[s])
{
return false;
}
}
for (s in o2)
{
numComparisons--;
}
return !numComparisons;
}

Related

How to merge arrays by index AS3

Seeking a way to merge arrays by index like so:
var a1:Array = ["a", "b", "c"];
var a2:Array = ["1", "2", "3"];
var a3:Array = result: ["a", 1, "b", 2, "c", 3]
I've tried concat, push, splice... not getting close. Thanks.
function arrayMerge(arr1:Array, arr2:Array):Array {
var o:Array = new Array();
for (var i:int=0; i<Math.max(arr1.length, arr2.length); i++) {
if (i<arr1.length) o.push(arr1[i]);
if (i<arr2.length) o.push(arr2[i]);
}
return o;
}
Thanks Payam for answer and non-judgement. Here's how I applied your work:
var arr1:Array = ["question1", "question2", "question3"];
var arr2:Array = ["answer1", "answer2", "answer3"];
var o:Array = new Array();
for (var i:int=0; i<Math.max(arr1.length, arr2.length); i++) {
if (i<arr1.length) o.push(arr1[i]);
if (i<arr2.length) o.push(arr2[i]);
}
trace(o); //(question1,answer1,question2,answer2,question3,question3)
#AndyH :
payamsbr is right, but you may work with Vectors or Arrays
Perhaps tl; dr; but this is the principle.
If You want to understand something try those possibilities.
If you don't, just copy and paste some shorter code ;)
var v1:Vector.<String> = new <String>["a", "b", "c"];
var v2:Vector.<uint> = new <uint>[1, 2, 3]; // why do you use String here and not uint?
// if you want to convert a uint to a String, use myUint.toString();
function convertVectorToArray(v1:Vector.<String>,v2:Vector.<uint>):Array{
var mergedArray:Array = new Array();
if (v1.length != v2.length){
throw(new Error(" ***ERROR : the two Vectors or Arrays have not the same lenfgth!"));
}else{
for(var i:uint = 0; i <v1.length ; i++){
mergedArray.push(v1[i]);
mergedArray.push(v2[i]);
}
}
return(mergedArray);
}
function mergeVectors(v1:Vector.<String>,v2:Vector.<uint>):Vector.<Object>{
var mergedVector:Vector.<Object> = new Vector.<Object>();
if (v1.length != v2.length){
throw(new Error(" ***ERROR : the two Vectors or Arrays have not the same length!"));
}
for(var i:uint = 0; i <v1.length ; i++){
mergedVector.push(v1[i] as String);
mergedVector.push(v2[i] as uint);
}
return(mergedVector);
}
var mergedArray:Array = (convertVectorToArray(v1,v2));
var mergedVector:Vector.<Object> = (mergeVectors(v1,v2));
function listArray(arr:Array):String{
var str: String="";
if ((v1.length*2) != (v1.length + v2.length)){
throw(new Error(" ***ERROR : the two Vectors or Arrays have not the same length!"));
}else{
for (var i:uint = 0; i < arr.length ; i++){
str+="typeof(arr[" + i + "]) = " + (typeof(arr[i]) as String).toUpperCase() + ", value = " + arr[i] + "\n";
}
}
return str;
}
function listVector(vect:Vector.<Object>):String{
var str: String = "";
if ((v1.length*2) != (v1.length + v2.length)){
throw(new Error(" ***ERROR : the two Vectors or Arrays have not the same length!"));
}else{
for (var i:uint = 0; i < vect.length ; i++){
str+="typeof(vect[" + i + "]) = " + (typeof(vect[i]) as String).toUpperCase() + ", value = " + vect[i] + "\n";
}
}
return str;
}
trace(listArray(mergedArray));
trace(listVector(mergedVector));
You may add a sort() method if You need it (you didn't told about it)
And Always throw an Error if the 2 Arrays or Vectors don't have the same length!
Throwing an Error is the best way to understand if something goes wrong...
This will avoid You a lot of time if You need to debug Your code!!!
As You can see the output is the same, but if the Vector Class is used correctly, this is more efficient than an Array.
Output :
Since there's a Vector Class, I don't understand a lot of people who chose Arrays instead...
Of course Vector. is a nonsense, but I posted it anyway so You can figure You out the Vector Class.
Output is the same :
typeof(arr[0]) = STRING, value = a
typeof(arr[1]) = NUMBER, value = 1
typeof(arr[2]) = STRING, value = b
typeof(arr[3]) = NUMBER, value = 2
typeof(arr[4]) = STRING, value = c
typeof(arr[5]) = NUMBER, value = 3
typeof(vect[0]) = STRING, value = a
typeof(vect[1]) = NUMBER, value = 1
typeof(vect[2]) = STRING, value = b
typeof(vect[3]) = NUMBER, value = 2
typeof(vect[4]) = STRING, value = c
typeof(vect[5]) = NUMBER, value = 3
I forgot this easiest way if you really want an Array...
Quick done!
var ar1:Array = [1,2,3];
var ar2:Array = ["a","b","c"];
function merge(...arrays):Array {
var result:Array = [];
for(var i:int=0;i<arrays.length;i++){
result = result.concat(arrays[i]);
}
return result;
}
trace(merge(ar1, ar2));
// outputs : 1,2,3,a,b,c
Another possibility :
function populateObject(v1:Vector.<String>, v2:Vector.<uint>):Object{
var obj = new Object();
if ((v1.length*2) != (v1.length + v2.length)){
throw(new Error(" ***ERROR : the two Vectors or Arrays have not the same length!"));
}else{
for (var i:uint = 0; i < v1.length; i++){
obj[v2[i]] = v1[i];
}
}
return obj;
}
var o:Object = populateObject(v1,v2);
function listObject(someObj:Object):void{
var myObj:Object = someObj;
for (var i:String in someObj){
trace(someObj[i] + ": " + i);
}
}
listObject(o);
output =
a: 1
b: 2
c: 3
I think that You have a lot of possibilities to use here even it's my longer answer ;)
If You try those possibilities and understand them, this will certainty help You to think to find the best way to deal with Your issue.
But You may also copy and paste some shorter code.
I just wanted to show You that there's more than one answer.
If you understand this, You will be able to go further with coding.
Have fun ;)
Sincerely.
Nicolas
Best regards.
Nicolas.

AS3 printing the lowest value to a dynamic textfield

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

Get unique random numbers from 1-40

I want to get unique random numbers each time from nos 1-40 without using an array.Is there any optimised way to get this in action script 3.
No, you have to use permutation, as you have to record those numbers you've already generated. And using these numbers require a set of some kind, aka Array. It's possible to solve this issue by using other data types, but they will essentially narrow down to an array of some sort.
A simple permutation code looks like this:
class Permutation {
private var _a:Array; // or Vector.<int> if you like
private var n:int; // next element
public function Permutation() {
reset(1);
}
public function reset(size:int=100):void {
_a.length=0;
for (n=0;n<size;n++) _a.push(n);
for (n=0;n<size;n++) {
var x:int=Math.floor(size*Math.random());
if (x==n) continue;
var swap:int=_a[x];
_a[x]=_a[n];
_a[n]=swap;
}
n=0;
}
public function getNext():int {
if (n==_a.length) return -1; // or any error value
n++;
return _a[n-1];
}
}
No array.
var generatedNumberCount:int;
var generatedNumberRef:Object = {};
for(var i:int = 0; i < 150; i++)
{
var result:Number = generateRandomInt(50);
trace(result);
}
trace(generatedNumberCount)
function generateRandomInt(limit:int):Number
{
if(generatedNumberCount >= limit)
{
return NaN;
}
var output:int = Math.ceil(Math.random() * limit);
while(generatedNumberRef[output] != undefined)
{
output = Math.ceil(Math.random() * limit);
}
generatedNumberRef[output] = true;
generatedNumberCount++;
return output;
}

AS3: How do I hittest objects in one array with objects in another array

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

Filling open spaces in a grid top down

I am writing a match three engine and I succeed in creating the matching with using huge loops to find the matching items. Any ideas on how to fill the empty spaces with the items ( dropping down into the empty spaces ) and creating new items without excessive looping and if statements?
Here is my relavant code so far.
public var rows:uint = 8;
public var cols:uint = 7;
public var cell:Array = new Array();
public var plot:Array = new Array();
public var height:int;
public var width:int;
public var relativePositions:Array = [{name:'top', position:-1}, {name:'bottom', position:1}, {name:'left', position:rows*-1}, {name:'right', position:rows*1}];
public var dictionary:Dictionary = new Dictionary();
public var matches:Array = new Array();
public function createGrid(target:*, displayObject:*, spacer:int) : void {
var iterator:uint = 0;
for(var c:uint = 0;c<cols;c++){
for(var r:uint = 0;r<rows;r++){
cell[iterator] = createGamePiece();
Sprite(cell[iterator]).name = String(iterator);
Sprite(cell[iterator]).addEventListener(MouseEvent.CLICK, _handleGamePiece_CLICK);
Sprite(cell[iterator]).addEventListener(MouseEvent.MOUSE_OVER, _handleGamePiece_MOUSE_OVER);
Sprite(cell[iterator]).addEventListener(MouseEvent.MOUSE_OUT, _handleGamePiece_MOUSE_OUT);
cell[iterator].y = cell[iterator].height * r + (spacer*r);
cell[iterator].x = cell[iterator].width * c + (spacer*c);
GamePiece(cell[iterator]).positionX = cell[iterator].x;
GamePiece(cell[iterator]).positionY = cell[iterator].y;
GamePiece(cell[iterator]).positionRow = r;
GamePiece(cell[iterator]).positionCol = c;
target.addChild(cell[iterator]);
dictionary[String(iterator)] = cell[iterator]
iterator++
}
}
}
public function findRelativeMatches(targetSprite:Sprite) : void {
targetSprite.alpha = .5;
var rootPosition:Number = Number(targetSprite.name);
for ( var i:int = 0; i < relativePositions.length; i ++ ) {
var key:String = String(rootPosition + relativePositions[i].position);
// to do >> Not hardcoded to 'Pig'
if (findSprite(key) != null && GamePiece(targetSprite).color == GamePiece(findSprite(key)).color && GamePiece(findSprite(key)).found == false) {
var sprite:Sprite = findSprite(key);
sprite.alpha = .5;
GamePiece(sprite).found = true;
matches.push(sprite);
findRelativeMatches(sprite);
};
};
targetSprite.addEventListener(MouseEvent.MOUSE_OUT, function() : void {
if ( matches.length != 0 ) {
for ( var j:int = 0 ; j < matches.length ; j++ ) {
Sprite(matches[j]).alpha = 1;
GamePiece(matches[j]).found = false;
}
matches.splice(0);
}
});
}
public function findSprite(key:String) : Sprite {
var sprite:Sprite;
dictionary[key] != undefined ? sprite = dictionary[key] : null;
return sprite;
}
protected function _handleGamePiece_CLICK(event:MouseEvent):void
{
for ( var j:int = 0 ; j < matches.length ; j++ ) {
var sprite:Sprite = matches[j];
view.removeChild(matches[j]);
}
matches.splice(0);
}
public function createGamePiece() : Sprite {
var gamePiece:GamePiece = new GamePiece();
return gamePiece;
}
You want to collapse your grid downwards, right? The common algorithm is going from the bottom of every row upwards, having one index of the first empty space found, and the other for the first occupied space above empty space, then exchange those values once found, iterating to the top. But you don't store the grid in any accessible form! You should create a grid object, say a vector of vectors of sprites, and assign values in it as you move pieces around. Like this:
var GRID:Vector.<Vector.<Sprite>>; // this should be allocated at createGrid
// populate GRID with your sprites once generated:
// put the following into your inner loop in CreateGrid:
GRID[r][c]=cell[iterator];
// and the following into your removal of matches[] sprites:
GRID[GamePiece(sprite).positionRow][GamePiece(sprite).positionCol]=null; // release link from GRID
// now to move grid objects:
function DropAll():void {
var i:int;
var j:int;
for (i=GRID.length-1;i>=0;i--) {
var lastEmpty:int=-1;
for (j=GRID[i].length-1;j>=0;j--) {
if (GRID[i][j]) {
if (lastEmpty>0) {
GRID[i][lastEmpty--]=GRID[i][j];
// relocate your sprite properly here
GRID[i][j]=null;
} // else we're still at full part of grid, continue
} else if (lastEmpty<0) lastEmpty=j;
}
}
}
To properly instantiate GRID you need to allocate vectors of desired length that are filled with "null" values. Also, "GRID" itself is a Vector, and needs to be instantiated too.
GRID=new Vector.<Vector.<Sprite>>();
for (i=0;i<rows;i++) {
var a:Vector.<Sprite>=new Vector.<Sprite>(cols);
GRID.push(a);
}
After you do this, you fill the GRID by directly assigning links in it, like GRID[r][c]=gameObject;
This is actually what I wanted. A way to collapse WITHOUT iterating over the entire board. This way I JUST loop through the items that have been removed.
protected function _handleGamePiece_CLICK(event:MouseEvent):void
{
for ( var j:int = 0 ; j < matches.length ; j++ ) {
var oldSprite:Sprite = matches[j];
moveAllPiecesDown(oldSprite);
view.removeChild(oldSprite);
oldSprite = null;
}
matches.splice(0);
}
private function moveAllPiecesDown(oldSprite:Sprite):void
{
var piecesAbove:int = GamePiece(oldSprite).positionRow;
var index:int = int(oldSprite.name);
for( var i:int = 0; i < piecesAbove; i ++ ) {
var spriteAbove:Sprite = Sprite(view.getChildByName(String(index-(1+i))));
if(spriteAbove) {
spriteAbove.y = spriteAbove.y + spriteAbove.height + 1;
spriteAbove.name = String(Number(spriteAbove.name)+1);
GamePiece(spriteAbove).textField.text = spriteAbove.name;
delete dictionary[spriteAbove.name];
dictionary[spriteAbove.name] = spriteAbove;
}
}
}