I need help. I am making something like dictionary (but you are filling it by yourself). I need fast search for words in it. I need to use Object or Array (because Dictionary is not supported by JSON. There is option to save file). I have this code, but I am afraid it is not so optimized to search fast when there will be a lot of words in array. Please help.
public function Search (string:String,section:String = Wordbook.NEWW):int
{
var str:String = string.toUpperCase();
for (i = 0; i < NewWords.length; i++)
{
if (NewWords[i].toUpperCase.indexOf(str) > -1)
{
return i;
}
}
return -1;//If not found
}
And example how it should work: (SearchTxt - text field, user should type here word he need to find; WB - Wordbook class; WB.NewWords & WB.NewWordsT - Arrays in this class)
var index:int = WB.Search(SearchTxt.text,Wordbook.NEWW);
if(index>-1){
WordTxt.text = WB.NewWords[index];
TranslationTxt.text = WB.NewWordsT[index];
} else {
dispatchEvent(new EventWithMessage(EventWithMessage.ERROR,{error:"No match!"}));
}
There are two solutions here, depending on your use-cases.
Solution 1: Leave it alone!
If the biggest dictionary you're going to build is only a hundred words or so, performance will be fine with a linear search.
Solution 2: Implement a binary search algorithm
If you do need to deal with large dictionaries of thousands of words, a binary search algorithm will offer improved performance. For this, you'll need to guarantee that your search array is sorted.
Something like this should do the trick:
public function Search (string:String,section:Array = NewWords, offset:int = 0):int
{
if (section.length == 0) {
return -1;
}
var str:String = string.toUpperCase();
var firstCharCode:int = str.charCodeAt(0);
var middleWord:String = section[section.length / 2];
var middleWordCharCode:int = middleWord.charCodeAt(0);
if (middleWord.substr(0,str.length) == str) {
return (section.length / 2) + offset;
} else {
var comparison:int = str.localeCompare(middleWord);
if (comparison < 0) {
return Search(string, section.splice(0, section.length / 2), offset);
} else { // then comparison > 0
var newOffset:int = offset + section.length / 2;
return Search(string, section.splice(section.length / 2, section.length-1), newOffset);
}
}
}
Related
I have 2 arrays, 1 is a master (bookArray) and the other an update list (stockBooksArray). I use the loop below to import new data into waster array using a unique record (isbn) that exists in both arrays.
Is there a way to improve the performance of the loop below?
The update array can contain a different count to the master, sometimes more, sometimes less.
for i in 0...stockBooksArray.count {
let StockFiltered = stockBooksArray.filter{$0.isbn == bookArray[i].isbn}
if StockFiltered.count != 0 {
bookArray[i].stockAmount = StockFiltered[0].Stock
bookArray[i].unitCost = StockFiltered[0].Cost
bookArray[i].dues = StockFiltered[0].dues
bookArray[i].stockRRP = StockFiltered[0].RRP
}
}
Thanks,
Anthony
Yes there is. Right now you are looping through the bookArray array once for every stockBooksArray object. That's O(N*M) which is pretty bad.
Instead, if you can sort both arrays by ISBN, then you can step through both at the same time, visiting each element only once which would be O(N). Considerably faster, even if you include the time to sort them.
I threw the code below together pretty quick. It assumes that both arrays are sorted by isbn. I think you will find this considerably faster.
var j = 0
for book in stockBooksArray {
while bookArray[j].isbn < book.isbn && j < bookArray.count {
++j;
}
if j == bookArray.count {
break
}
else if bookArray[j].isbn == book.isbn {
bookArray[j].stockAmount = StockFiltered[0].Stock
bookArray[j].unitCost = StockFiltered[0].Cost
bookArray[j].dues = StockFiltered[0].dues
bookArray[j].stockRRP = StockFiltered[0].RRP
}
}
If you are just adding new ones you could get an NSMutableSet of the ISBNs for each, perform a set minusSet: operation removing the bookArray ISBNs from stockBooksArray and then just add the resulting ISBNs to bookArray.
Untested concept code, convert to Swift as needed:
NSMutableSet *stockBooksSet = NSMutableSet setWithArray:[stockBooksSet valueForKey:#"isbn"];
NSSet *booksSet = NSSet setWithArray:[bookArray valueForKey:#"isbn"];
[stockBooksSet minusSet: booksSet];
for (NSString *isbn in stockBooksSet) {
// add book with ism to booksSet
}
Updated code using Daniel T. method.
bookArray.sort { (lhs, rhs) in return lhs.isbn < rhs.isbn }
stockBooksArray.sort { (lhs, rhs) in return lhs.isbn < rhs.isbn }
var j = 0
for book in stockBooksArray {
while bookArray[j].isbn < book.isbn && j < bookArray.count {
++j
}
if j == bookArray.count {
break
}
else if bookArray[j].isbn == book.isbn {
bookArray[j].stockAmount = book.Stock
bookArray[j].unitCost = book.Cost
bookArray[j].dues = book.dues
bookArray[j].stockRRP = book.RRP
}
}
From 5-6 seconds on iPad2 to almost instant.
Many thanks,
Anthony
I am developing a game that has a winning combination array:
var allwinning = [
['000','010','020'],
['000','100','200'],
['000','001','002'],
['000','101','202'],
['000','011','022'],
['000','110','220']];
The player will need to pick more than 3 numbers randomly. If all numbers are within any of the combinations in allwinning, the player wins.
For example, if the player picks '111','110','000','220', the player will win because allwinning[5] has the combination['000','110','220'].
My question is, what is the best way to do this winning loop? I cannot figure out the optimum way to do this.
Currently, I have a playerpick array to keep what player had picked and possiblewin array:
var playerpick = new Array(['111','110','000','220']);
var playerpicksingle = playerpick[0];
var possiblewin = new Array([]);
Then I go through a loop to capture out the possible win combination first:
for(var i=0 ; i < allwinning.length - 1 ; i++)
{
for(var j=0 ; j <3 ; j++)
{
if(allwinning[i][j]==playerpicksingle)
{
possiblewin.Push(allwinning[i]);
}
}
}
Then I am stuck at this point. I really don't know what else to do.
I can think of two ways. One requires you to change your data structure and the other doesn't.
Without changes:
Sort the user input:
pickedNumbers.sort();
and start comparing. By sorting the values beforehand you know when you can back out and continue with the next set of numbers, i.e. you can back out early and don't have to compare all the values (in the average case).
function wins(picked, winning) {
var winningSet = [];
for (var i = 0; i < winning.length && winningSet.length < 3; i++) {
var set = winning[i];
winningSet = [];
var j = 0;
var k = 0;
while (j < set.length && k < picked.length && winningSet.length < 3) {
if (picked[k] === set[j]) {
winningSet.push(set[j]);
j++; // advance to next element in winning set
} else if (picked[k] > set[j]) {
// continue with the next set
break;
}
// maybe the next element in players picks will match
k++;
}
}
return winningSet.length === 3 ? winningSet : false;
}
The worst case scenario of this solution is O(n*m*l), but since the input is sorted, the average case will be better.
DEMO
With Array#some and Array#every the code becomes much more concise, though it looses the advantage of using sorted input. If your arrays are small it won't make a difference though:
function wins(picked, winning) {
return winning.some(function(set) {
return set.every(function(val) {
return picked.indexOf(val) !== -1;
});
});
}
It also won't give you the actual numbers that matched. The runtime is the same.
The second way would be to build some kind of trie instead of using an array of arrays:
var allwinning = {
'000': {
'010': {
'020': true
},
'100': {
'200': true
},
// ...
}
};
The structure should also be sorted, i.e. the keys of a level are all smaller then the keys of its sublevel etc.
Sort the user input as well and iterate over it. Whenever you found a matching key, you go one level deeper until you have three matches:
function wins(picked, winning) {
var winningSet = [];
for (var i = 0; i < picked.length && winningSet.length < 3; i++) {
if (picked[i] in winning) {
winningSet.push(picked[i]);
winning = winning[picked[i]];
}
}
return winningSet.length === 3 ? winningSet : false;
}
This solution has the worst case scenario of O(n), where n is the number of values the user picked (not taking into account the time it takes to test whether an object contains a specific property name. Often this is assumed to constant).
DEMO
I have two arrays, namely combo and truecombo. The user fills the combo with MovieClips by clicking on various buttons on the stage, truecombo is the correct combination.
At any given point (enterFrame) Flash is checking whether the two are the same, if yes, then do some stuff. For the time being this is my code (altered several times, like with Typecasting the indices, adding .parent at the end of combo[o] etc. 2 things will happen, either one or the other.
Either the statement will not be satisfied, at which point the adding and chopping of the combo array will continue, or the condition will be instantly met when combo.length = 6. Check my code.
UPDATE: I have a dropbox file with my current code. Click this for FLA link and here is the SWF link stripped down as always for ease and security.
/*stage.*/addEventListener(Event.ENTER_FRAME, checkthis);
function checkthis(e:Event)
{
for(var o:int=0;o<= combo.length; o++)
{
if((combo[o] == truecombo[o]) && (combo.length==truecombo.length))
{
equal=true;
}
}
if (equal==true)
{
stage.removeEventListener(Event.ENTER_FRAME, checkthis);
endSeq();
}
}
function endSeq():void
{
bravo.play();
for (var i:int = 0; i < combo.length; i++)
{
var element:DisplayObject = combo[i];
element.parent.removeChild(element);
}
firebb.gotoAndPlay(2);
windbb.gotoAndPlay(2);
spiritbb.gotoAndPlay(2);
earthbb.gotoAndPlay(2);
}
This is how I push my new elements to the combo array.
function add(element:DisplayObject)
{
twist.gotoAndPlay(2);
element.width = WIDTH;
element.height = HEIGHT;
if (this.combo.length >= MAX_ELEMENTS)
{
removeChild(this.combo.shift());
}
this.combo.push(element as DisplayObject);
this.addChild(element);
this.reorder();
}
function reorder()
{
for (var i:int = 0; i < combo.length; i++)
{
var element:DisplayObject = combo[i];
element.x = OFFSET_X + (i * SEP_X);
element.y = OFFSET_Y;
}
}
And this is how I have my truecombo and its contents created.
var fireb:firebtn = new firebtn();
var spiritb:spiritbtn = new spiritbtn();
var earthb:earthbtn = new earthbtn();
var windb:windbtn = new windbtn();
var combo:Array=new Array();
const truecombo:Array = [fireb,windb,spiritb,windb,earthb,fireb];
Sorry for the lack of comments, I'd guess it's pretty self-explanatory. Thanks in advance.
I believe combo[o] & truecombo[o] are two instances of the same class & you want them to be matched. If that is the case you may consider :
getQualifiedClassName(combo[o]) == getQualifiedClassName(truecombo[o])
To match the way you did, you must ensure the objects lying inside truecombo be referring to the same ones on stage & not new instances.
EDIT:
It seems you do not break the loop when the match is a success. Use this instead :
function checkthis(e:Event)
{
for(var o:int=0;o<= combo.length; o++)
if((combo[o] == truecombo[o]) && (combo.length==truecombo.length)) {
equal=true;
break;
}
if (equal) {
stage.removeEventListener(Event.ENTER_FRAME, checkthis);
endSeq();
}
}
Here's a really simple loop:
var equal:Boolean=true
if(combo.length == truecombo.length) {
for(var i:int=0; i<combo.length; i++) {
if(combo[i] != truecombo[i]) {
equal=false;
break;
}
}
} else {
equal=false;
}
if(equal) {
//do whatever
}
This assumes both are equal, until we find out otherwise. So if the lengths are different, they are not equal. If the ith element is different, they are not equal.
In the end, you check if your flag equal is true and do whatever you want to do.
How do you randomize an array using actionscript 3?
There is a short version using Array.sort() function:
var arr : Array = [0,1,2,3,4,5,6,7,8,9];
function randomize ( a : *, b : * ) : int {
return ( Math.random() > .5 ) ? 1 : -1;
}
trace( arr.sort( randomize ) );
If you don't get "enough" randomness you can sort twice :)
EDIT - explanation line by line:
For Array class method sort() you can pass not only sort options like Array.CASEINSENSITIVE, Array.DESCENDING and so on but also your own custom compare function reference (a callback) that accepts two parameters (two elements from array to compare). From AS3 documentation:
A comparison function should take two arguments to compare. Given the elements A and B, the result of compareFunction can have a negative, 0, or positive value:
A negative return value specifies that A appears before B in the sorted sequence.
A return value of 0 specifies that A and B have the same sort order.
A positive return value specifies that A appears after B in the sorted sequence.
Note: compare function parameters might be typed (if your array is typed) and have any name you want eg.:
function compareElements ( elementA : SomeClass, elementB : SomeClass ) : int;
This method is very useful when you need to sort array elements by their special properties. In randomization case compareFunction randomly returns -1, 0 or 1 and makes array elements to switch their places (indices). I have found that better randomization (in my subjective and mathematically untested opinion) is when method returns only -1 and 1. Also have in mind that sorting function with custom compare function doesn't compare elements sequentially so in some special cases randomization results may differ from what you might expect.
There's a better way that will also allow you to randomize the array in place, if you need that, and it will not make you create more then a single copy of your original array.
package
{
import flash.display.Sprite;
public class RandomizeArrayExample extends Sprite
{
public function RandomizeArrayExample()
{
super();
testDistribution();
}
private function testDistribution():void
{
var hash:Object = { };
var tester:Array = [1, 2, 3, 4];
var key:String;
for (var i:int; i < 1e5; i++)
{
randomize(tester);
key = tester.join("");
if (key in hash) hash[key]++;
else hash[key] = 1;
}
for (var p:String in hash) trace(p, "=>", hash[p]);
}
private function randomize(array:Array):Array
{
var temp:Object;
var tempOffset:int;
for (var i:int = array.length - 1; i >= 0; i--)
{
tempOffset = Math.random() * i;
temp = array[i];
array[i] = array[tempOffset];
array[tempOffset] = temp;
}
return array;
}
}
}
I had an alternative requirement where i wanted to randomly insert lots of source arrays into a target array randomly. Like Rytis i'm a big fan of the forEach, map and sort functions on Arrays.
var randomInsert:Function = function callback(item:*, index:int, array:Vector.<MyItem>):void
{
var j:Number = Math.floor(Math.random() * targetArray.length);
targetArray.splice(j,0,item);
}
targetArray = new Vector.<MyItem>();
sourceArray1.forEach(randomInsert, this);
sourceArray2.forEach(randomInsert, this);
here's an easier function. Works also on multidimensional arrays
function randomizeArray(array:Array):Array
{
var newArray:Array = new Array();
while (array.length > 0)
{
var mn=Math.floor(Math.random()*array.length)
newArray[newArray.length]=array[mn]
array.splice(mn,1)
}
return newArray;
}
I found this very helpful. I hope it can help you too.
// Array to Randomize
var firstArray:Array = ["One","Two","Three","Four","Five","six","seven","eight","nine","ten"];
trace(firstArray); // Prints in order
var newArray:Array = new Array();
function randomizeArray(array:Array):Array
{
var newArray:Array = new Array();
while (array.length > 0)
{
newArray.push(array.splice(Math.floor(Math.random()*array.length), 1));
}
return newArray;
}
var randomArray:Array = randomizeArray(firstArray);
trace(randomArray); // Prints out randomized :)
If you need your array to be shuffled (your elements can not repeat). You could use this function:
/**
* Shuffles array into new array with no repeating elements. Simple swap algorithm is used.
*/
public function shuffleArray(original:Array):Array
{
// How many swaps we will do
// Increase this number for better results (more shuffled array, but slower performance)
const runs:int = original.length * 3;
var shuffled:Array = new Array(original.length);
var i:int;
var a:int;
var b:int;
var temp:Object;
// Copy original array to shuffled
for(i=0; i<shuffled.length; i++){
shuffled[i] = original[i];
}
// Run random swap cycle 'runs' times
for(i=0; i<runs; i++){
// There is a chance that array element will swap with itself,
// and there is always small probability it will make your shuffle
// results not that good, hence try to experiment with
// different runs count as stated above
a = Math.floor(Math.random() * original.length);
b = Math.floor(Math.random() * original.length);
// Swap messages
temp = shuffled[a];
shuffled[a] = shuffled[b];
shuffled[b] = temp;
}
return shuffled;
}
Usage:
var testArray:Array = ["Water", "Fire", "Air", "Earth"];
trace(shuffleArray(testArray).concat());
this is how I randomize my array of 36 cards for a memory game
const QUANT_CARTAS: int = 36;
//get the 36 numbers into the array
for (var i: int = 0; i < QUANT_CARTAS; i++)
{
cartas.push(i);
}
//shuffles them =)
for (var moeda: int = QUANT_CARTAS - 1; moeda > 0; moeda--)
{
var pos: int = Math.floor(Math.random() * moeda);
var carta: int = cartas[moeda];
cartas[moeda] = cartas[pos];
cartas[pos] = carta;
}
// and add them using the random order...
for (i = 0; i < QUANT_CARTAS; i++)
{
var novaCarta: Carta = new Carta();
novaCarta.tipoCarta = cartas[i];
etcetcetc.............
}
choose random string from array
function keyGenerator(len:Number):String
{
function randomRange(minNum:Number, maxNum:Number):Number
{
return (Math.floor(Math.random() * (maxNum - minNum + 1)) + minNum);
}
var hexArray = ['0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'];
var key = "";
for (var i=0; i<len; i++)
{
key += hexArray[randomRange(0,hexArray.length-1)];
}
return key;
}
usage:
trace(keyGenerator(16));
this is an array of objects that i want to alphabetize:
var streets:Array = new Array();
streets.push({name:"Édouard-Montpetit"});
streets.push({name:"Alexandre de Sève"});
streets.push({name:"Van Horne"});
streets.push({name:"Atwater"});
now i'll sort my array:
streets.sortOn("name", Array.CASEINSENSITIVE);
//Sorted
Alexandre de Sève
Atwater
Van Horne
Édouard-Montpetit
the accent above the E in Édouard-Montpetit, and any other first letter with a non-english accent is sorted after Z.
any ideas how i can sort this correctly? i do not have access to the named data.
I know this is late, but for anyone going through this answer, you could pass a Collator
object to the Array.sort() method. A simple example from the documentation:
var words:Array = new Array("coté", "côte");
var sorter:Collator = new Collator("fr-FR", CollatorMode.SORTING);
words.sort(sorter.compare);
trace(words);// côte,coté
Hope this helps
I don't think you can do it with sortOn, as there's no way to tell flash to use a particular collaction for sorting text (at least, not that I'm aware of).
However, you could use sort and a custom sort function.
In this sort function, basically you want to strip all accents and do a case insensitive comparation. Replacing diacritics is easy and after that, you can safely use < and > for comparing the strings. A sort function is called by sort with two of the items to be sorted at a time. It should return a negative number if the first passed items sorts first , a possitive number if the second comes first and 0 if they sort equal.
function sortText(obj1:Object,obj2:Object):int {
var a:String = replaceDiacritics(obj1.name);
var b:String = replaceDiacritics(obj2.name);
if(a < b) {
return -1;
} else if(b < a) {
return 1;
} else {
return 0;
}
}
function replaceDiacritics(str:String):String {
str = str.toLowerCase();
str = str.replace(/á/g,"a");
str = str.replace(/é/g,"e");
str = str.replace(/í/g,"i");
str = str.replace(/ó/g,"o");
str = str.replace(/ú/g,"u");
str = str.replace(/à/g,"a");
str = str.replace(/è/g,"e");
str = str.replace(/ì/g,"i");
str = str.replace(/ò/g,"o");
str = str.replace(/ù/g,"u");
return str;
}
streets.sort(sortText);
A couple of notes about this. I know this method won't work for Spanish, as you have ñ, which is considered a letter on its own (not a regular n with a funny mark) and comes after n and before o. So, it's not possible to just replace accents and do a < / > compare. I think this is not a problem in French, but I could be wrong (not sure how Ç / ç is considered for sort purposes, for instance). Also, note that I haven't replaced all possible diacritics, so you'd want to add circumflexes (^) and umlauts (¨) to replaceDiacritics as necessary.
Edit
For a table based approach you could try something like the following. Each letter is assigned a number that reflects the sort order. As long as you can assume that any letter will have an absolute sort order (that is, context won't change how this works, which is not the case on some languages), it should give you good results.
Out of laziness, I built the table with a loop and just did what was neccesary to put "Ñ" between "n" and "o". I'm not considering any diacritics for sorting purposes, so they have the same value than their unaccented counterpart. But you could change this table as neccesary. Also, this table probably should be hardcoded for the required locale, but this code is just to give you an idea of how you could do this, not a full implementation (and it's probably not entirely correct from a purist perspective, but I think it could do the job). Also, in case we find a character that is not mapped, I'm falling back to its code point to determine how it sorts.
var sortTable:Object = buildSortTable();
function buildSortTable():Object {
var sortTable:Object = {};
var char:String;
var offset:int = 0;
for(var i:int = 1; i < 256; i++) {
char = String.fromCharCode(i);
if(char == "Ñ" || char == "ñ") {
offset--;
continue;
}
sortTable[char] = i + offset;
if(char == "N") {
sortTable["Ñ"] = sortTable["N"] + 1;
offset++;
}
if(char == "n") {
sortTable["ñ"] = sortTable["n"] + 1;
offset++;
}
}
sortTable["Á"] = sortTable["À"] = sortTable["Ä"] = sortTable["Â"] = sortTable["A"];
sortTable["É"] = sortTable["È"] = sortTable["Ë"] = sortTable["Ê"] = sortTable["E"];
sortTable["Í"] = sortTable["Ì"] = sortTable["Ï"] = sortTable["Î"] = sortTable["I"];
sortTable["Ó"] = sortTable["Ò"] = sortTable["Ö"] = sortTable["Ô"] = sortTable["O"];
sortTable["Ú"] = sortTable["Ì"] = sortTable["Ü"] = sortTable["Û"] = sortTable["U"];
sortTable["á"] = sortTable["à"] = sortTable["ä"] = sortTable["â"] = sortTable["a"];
sortTable["é"] = sortTable["è"] = sortTable["ë"] = sortTable["ê"] = sortTable["e"];
sortTable["í"] = sortTable["ì"] = sortTable["ï"] = sortTable["î"] = sortTable["i"];
sortTable["ó"] = sortTable["ò"] = sortTable["ö"] = sortTable["ô"] = sortTable["o"];
sortTable["ú"] = sortTable["ù"] = sortTable["ü"] = sortTable["û"] = sortTable["u"];
return sortTable;
}
function sortText(obj1:Object,obj2:Object):int {
var a:String = obj1.name.toLowerCase();
var b:String = obj2.name.toLowerCase();
var len_a:int = a.length;
var len_b:int = b.length;
var char_a:String;
var char_b:String;
var val_a:Number;
var val_b:Number;
for(var i = 0; i < len_a && i < len_b; i++) {
char_a = a.charAt(i);
char_b = b.charAt(i);
val_a = sortTable[char_a];
val_b = sortTable[char_b];
// this is just in case we have a letter that we haven't mapped...
// let's fall back to using its code point
if(isNaN(val_a)) {
val_a = char_a.charCodeAt(0);
}
if(isNaN(val_b)) {
val_b = char_b.charCodeAt(0);
}
if(val_a < val_b) {
return -1;
} else if(val_a > val_b) {
return 1;
}
}
// both strings are equal so far; so the sorter one (if any) must sort first
if(len_a < len_b) {
return -1;
} else if(len_a > len_b) {
return 1;
} else {
return 0;
}
}