Quickly Determining the Position of Data in an Array - actionscript-3

I have a data structure as the image below illustrates. I need to quickly figure out the index of the cells to the right or left of the highlighted cell group.
You can see in the code below I am naively looping through ALL cells at every index to determine if there is a cell at the requested index. This works great when I have a few (hundred) cells, but breaks down quickly when I have thousands of cells.
In this particular case the highlighted group is mobile, and can only move to the index before or after the previous/next occupied cell. So groupMinX/maxX is the minimum and maximum x value it can move based on the position of other cells in the row.
private var movingGroup:CellGroup; //selected group
public function getCellAtIndex(index:int):ICell
{
for each(var cell:ICell in cells)
{
if(cell.index==index)
return cell;
}
return null;
}
public function groupMinX(xPos:Number):Number
{
var index:int = xPos/cellSize;
var cellsOnLeft:Array = getAllCellsOnLeft(index-1);
if(cellsOnLeft.length > 0)
return cellsOnLeft[cellsOnLeft.length-1].x + cellSize;
return 0;
}
public function groupMaxX(xPos:Number):Number
{
var index:int = xPos/cellSize;
var cellsOnRight:Array = getAllCellsOnRight(index);
if(cellsOnRight.length > 0)
return cellsOnRight[0].x;
return (maxIndex)*cellSize;
}
private function getAllCellsOnLeft(ofIndex:int):Array
{
var index:int = 1;
var cells:Array = [];
while( ofIndex >= 0 )
{
var cell:ICell = getCellAtIndex(ofIndex);
if(cell && !movingGroup.containsCell(cell))
cells.unshift( cell );
ofIndex--;
}
return cells;
}
private function getAllCellsOnRight(ofIndex:int):Array
{
var index:int = 1;
var cells:Array = [];
while( index <= maxIndex )
{
var cell:ICell = getCellAtIndex( ofIndex + index );
if(cell && !movingGroup.containsCell(cell))
cells.push( cell );
index++;
}
return cells;
}
What I am looking for is an efficient method for scanning/tracking the cells. The array I am looping through doesn't actually contain the blank cells, but it has the cells with the index property.

Whoops in my tweet I added the wrong link. Use a linked list. Have your 'Cell' class implement a Linked List Node interface. No need to loop or use conditionals.
http://en.wikipedia.org/wiki/Linked_list

As the list is ordered, I would suggest you do a binary search to find your wanted cell. Then, rather than looping through the elements to the left and the right, simply slice the array to form two new arrays of the left and right side.
Something like so, parhaps? (please excuse any syntactical errors, I don't know actionscript...)
private function search(array:Array, index:int, low:int, high:int) :int
{
if (high < low)
return -1
var middle:int = low + ((high - low) / 2)
if (array[middle].index > index)
return search(array, index, low, middle - 1)
else if (array[middle].index < index)
return search(array, index, middle + 1, high)
else
return middle
}
private function sliceBitsOff(index:int)
{
var index:int = search(yourArray, 7, 0, yourArray.length-1)
var rightArray:Array = yourArray.slice(0, index - 1)
var leftArray:Array = yourArray.slice(index + 1, yourArray.length)
}

You could just pre-cache the indices of the cell on the left and the cell on the right ...
Your choice of a data structure is non-ideal. Please describe what you are trying to do at a higher level. Was this data structure given to you, or did you come up with it?

I'm not sure if I am missing something here but if this is a numerically index array of objects (cells),
I think you could do something like...
cellsArr[cellsArr.indexOf(cellObj1) - 1] // previous cell
cellsArr[cellsArr.indexOf(cellObj2) + 1] // get the cell after a "highlighted" cell

Related

AS3 novice struggles

var animal:String ="Cat";
var isFish:Boolean;
isFish = isItAFish(animal);
trace(isFish);
function isItAFish (animal:String):Boolean {
var fishArray:Array = new Array("haddock", "Trout", "Salmon", "Cod");
for(var i:int = 0; i < fishArray.length; i++){
if (fishArray[i] == animal){
return true;
break;
}
}
return false;
}
I just need help explaining this code guys and girls. The "isFish = isItAFish (animal); trace(isFish); is where I'm confused from.
//animal is a string that contains the value "Cat"
var animal:String ="Cat";
//isFish is a boolean that will be used as a flag
var isFish:Boolean;
//isFish value will be changed from the outcome of the function isItAFish with the animal value.
isFish = isItAFish(animal);
trace(isFish);
//This function requires 1 string parameter and returns a boolean.
function isItAFish (animal:String):Boolean
{
//fishArray is a list of all your possible fishes.
var fishArray:Array = new Array("haddock", "Trout", "Salmon", "Cod");
/*
We iterate the array to see if animal ("Cat") is inside the fishArray possible values.
This loop will run exactly the number of times of the array's content. In this case, 4 times.
*/
for(var i:int = 0; i < fishArray.length; i++)
{
/*
We are comparing the values of the fishArray against animal ("Cat").
fishArray[i] holds the value of the current loop count.
For example, the first loop will be fishArray[0] which is "haddock".
The 4th loop will contain the value "Cod".
*/
if (fishArray[i] == animal)
{
//If we find a match, we return 'true' and stop the loop.
return true;
break;
}
}
//IF the loop ends without any matches we return 'false'.
return false;
}

Searching array for letters case insensitive in AS3

I am an absolute newbie to ActionScript 3 and I need to create a function that searches an array of alphabets for a specific letter and return its index. I already found the indexOf which is perfect. But I need this search to be not case sensitive. Like if my array is this:
Array("a","B","c")
if I am looking for "A" the index returned would be 0.
Is there some predefined function for this?
I don't think there is a predefined function for this.
You can write your own, though. Use a for-loop and iterate through the array, using String.toLowerCase() to alter both the array value and the check value temporarily for checking.
Actually... asking you to write your own is asking you to shoot yourself in the foot for something like this. Create a class ArrayUtils, stuff this function in it.
/**
* Searches through an array for a case-insensitive string match.
* Attempts to imitate Array.indexOf where it can.
* #param arr The array to search through
* #param searchingFor The string to search for
* #param fromIndex Optional, an index to start searching at.
* #returns The index of the array that a match was found at (zero-indexed), or -1 if no match was found.
*/
public static function indexOfCaseInsensitiveString(arr:Array, searchingFor:String, fromIndex:uint = 0):int {
var lowercaseSearchString:String = searchingFor.toLowerCase();
var arrayLength:uint = arr.length;//Retrieving array length is expensive, optimization
for(var index:uint = fromIndex; index < arrayLength; index++){
var element:* = arr[index];
if(element is String && element.toLowerCase() == lowercaseSearchString){
return index;
}
}
return -1;//It wasn't found in the array.
}
You could then call it via ArrayUtils.indexOfCaseInsensitiveString(<your array>, <your string>). The fromIndex is just there to mimic the regular indexOf (principle of least surprise; if I say it's "indexOf with case-insensitive string search", why would that affect fromIndex?).
Regular Expressions may be used to achieve this using the case insensitive flag.
function findTargetIndex(needle:String,heystack:Array):int
{
var straw:String;
var magnet:RegExp = new RegExp(needle,"i");//"i" flag makes the search case insensitive.
//can also be done with magnet.ignoreCase = true;
for (var x:int = 0; x < heystack.length; x++)
{
straw = heystack[x];
if (straw.search(magnet) > -1) return x;
}
return -1;
}
The above function would search the array looking for any element that contains your search string. You can limit it to matching whole strings only by altering the RegExp;
var magnet:RegExp = new RegExp("^"+needle+"$","i");
^ matches the beginning of a string, $ matches the end, meaning the whole string must match the whole expression.
disclaimer: I havn't compiled this code, this is just to demonstrate how I would go about it.
you should check nulls and non-string variables. you should use Vector.<String> instead of Array
nope, there is none.
the simplest way:
function getCaseInsensitiveIndexOf(needle:String, haystack:Array):int
{
var index:int = -1;
if(needle != null)
{
index = haystack.indexOf(needle.toUpperCase());
if(index == -1)
index = haystack.indexOf(needle.toLowerCase());
}
return index;
}
// bellow is my first answer, but its overkill
you have to normalize the content of the array beforehand.
you possibly want to normalize a copy of it actually, so you don't loose any detail.
function getNormalizedVersionOf(target:Array):Array
{
var copy:Array = [];
for(var i:int = 0; i < target.length; i++)
{
copy[i] = (target[i] as String).toUpperCase(); // or .toLowerCase()
}
return copy;
}
and when you search, you search on the normalized copy
function getItemIndex(searchFor:String):int
{
var s:String = searchFor.toUpperCase();
return mNormalized.indexOf(s); //if you have the copy array as a class member called mNormalized
}

How can I check if an array contains another array?

At first sight it's very simple, but I'm having some problems to do this without using a lot of nested loops.
Example:
var father:Array = new Array(0,1,2,3,4,5);
var son:Array = new Array(3,4,5);
father.contains(son) // returns true or 4(the starting index if the contained array)
ActionScript 3 actually supports some slightly crazy stuff, due to the fact that, in the early days, Adobe/Macromedia were trying to make it compliant with Ecmascript.
So... you can do this:
var a1:Array = [1,2,3,4,5,6,7,8,9];
var a2:Array = [3,4,5];
// borrow String's indexOf function, and it magically works on Arrays
// but rename it because Array already has a different indexOf function
a1.indexOf2 = String.prototype.indexOf;
trace(a1.indexOf2(a2) > -1); // true
But you need to be a little bit careful because it will convert all the elements to Strings for the equality test. For primitives, it mostly won't matter but it will break badly with objects as they'll all be converted to "[object Object]" or to whatever their toString() returns.
Also, if you wanted to use the actual index for anything, rather than just checking it's not -1, you have to divide by two, as the number is double what you'd expect. I don't exactly know why this is :)
If you need something more general and reliable, you'd be better off writing a function to do an explicit search. This is a quick example, which I just wrote so could easily be bug-ridden:
public function find(haystack:Array, needle:Array):int
{
var index:int = -1;
while(index <= haystack.length - needle.length)
{
index++;
index = haystack.indexOf(needle[0], index);
for( var i:int = 1; i<needle.length; i++)
{
if(haystack[index+i] != needle[i])
{
continue;
}
}
if( i == needle.length)
{
return index;
}
}
return -1;
}
Try this for simplicity:
// Determines if an array contains en element (similar to the PHP function with the same name)
public function in_array(needle:*, haystack:Array):Boolean
{
for each (var element:* in haystack)
{
if (element == needle) {return true;}
}
return false;
}

Return one item from filtered Array

I want to return just 1 item in the filtered array
my code is
private function audioProgress(event:Event):void{
var wordindex:int=0;
function filterFun(element:int, index:int, array:Array):Boolean {
return (element < soundChannel.position);
}
var arr:Array=soundPositions.filter(filterFun);
}
I want "arr" to contains just one item
How can I do that
If I read your code correctly, you are trying to sync to a playing sound? Then using Array.filter is inefficient - you only need to keep track of the most recently passed marker.
Assuming that your soundPositions array is numerically sorted, this can be done in a simple loop:
private var current : int = 0;
private function audioProgress(event:Event):void{
while( current < soundPositions.length -1 &&
soundPositions[current+1] < soundChannel.position )
current++;
doStuffWith(soundPositions[current]);
}
This way, there will only be one iteration of the array - total. The while loop starts at the current index, and it will exit when the value is greater than or equal to the sound's position, so current will always point to the last item the (virtual) playhead has passed.
You need the index of the item you want. If you just want the first item, use:
arr[0];
Another variant to get one item from the initial array:
private function audioProgress(event:Event):void{
var wordindex:int=0;
var firstRequiredItemIndex:int = -1;
function filterFun(element:int, index:int, array:Array):Boolean {
if (element < soundChannel.position)
{
firstRequiredItemIndex = index;
return true;
}
else
{
return false;
}
}
if (soundPositions.some(filterFun))
{
// Your element
soundPositions[firstRequiredItemIndex];
}
}
The function 'some' executes a test function on each item in the array until an item is reached that returns true. So there is no need to check the whole array.
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/Array.html#some%28%29

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