More Efficient Way to Accomplish This? - javascript

I need to know the level of a player using the amount of exp he has and the exp chart. I want to do it the most efficient way possible. This is what I got. Note: The real expChart has thousands of levels/index. All the values are in increasing order.
var expChart = [1,10,23,54,65,78,233,544,7666,22224,64654,456456,1123442];
/*
lvl 0: //0-1[ exp
lvl 1: //[1-10[ exp
lvl 2: //[10-23[ exp
*/
getLvlViaExp = function(exp){
for(var i = 0 ; i < expChart.length ; i++){
if(exp < expChart[i]) break;
}
return i;
}
This is a more efficient way to do it. Every x steps, (6 i the example, probably every hundreds with real chart), I do a quick comparation and jump to approximative index, skipping many indexes.
getLvlViaExp = function(exp){
var start = 0;
if(exp > 233) start = 6;
if(exp > 1123442) start = 12;
for(var i = start ; i < expChart.length ; i++){
if(exp < expChart[i]) break;
}
return i;
}
Is there an even better way to do this?
SOLUTION:
Array.prototype.binarySearch = function(value){
var startIndex = 0,
stopIndex = this.length - 1,
middle = Math.floor((stopIndex + startIndex)/2);
if(value < this[0]) return 0;
while(!(value >= this[middle] && value < this[middle+1]) && startIndex < stopIndex){
if (value < this[middle]){
stopIndex = middle - 1;
} else if (value > this[middle]){
startIndex = middle + 1;
}
middle = Math.floor((stopIndex + startIndex)/2);
}
return middle+1;
}

The best algorithm for searching is binary search which is O(lg n) (unless you can do it with a hashing search which is O(c).
http://www.nczonline.net/blog/2009/09/01/computer-science-in-javascript-binary-search/
Basically jump to the middle of your chart ( n / 2). Is you experience higher or lower from that number. If higher jump to the middle higher half. If lower jump to the middle of the lower half: Compare and repeat until you find what you're looking for.

var expChart = [1,10,23,54,65,78,233,544,7666,22224,64654,456456,1123442];
getLvlViaExp = function(exp){
var min=0;
var max=expChart.length-1;
var i;
while(min <=max){
i=Math.round((min+max)/2);
//document.write("<br />"+i+":"+min+":"+max+"<br />");
if(exp>=expChart[i] && exp <=expChart[i+1]) {
break;
}
if(exp>=expChart[i]){
min=i+1;
}
if(exp<=expChart[i]){
max=i-1;
}
}
return i;
}
document.write(getLvlViaExp("10"));
I have tested it and it seems to work pretty well. If you want to see how many steps it actually goes through to get to the answer, uncomment the document.write in the while loop. It was kind of fascinating watching it.

Related

Javascript Slice: Is is possible to use 2 parameters to select the last character of a string?

The Question
If I want to use slice to get the last character of a string I would do..
lastCharacter = theString.slice(-1);
Is it possible to select the last character with both a start and end numerical parameter? Like this..
var start = -1;
var end = -0; // nonsensical, I realize
lastCharacter = theString.slice(start, end);
The Answer
There's no special -0 digit
The Reason I Asked
The reason I asked is I was looking to use variables for both parameters which would decrement on each loop as I built a new variable which would be the reverse of theString. Until atomrc's answer I didn't think to simply use theString.length and work back from the last character rather than using -1 to start with the last character as I do in the example below (which requires an if statement for the first iteration of the for loop).
It was for the third question on codeschools arcade javascript test 'Check if string is a palindrome'
My solution was..
function checkPalindrome(inputString) {
var characterTotal = inputString.length;
var inputStringStringified = inputString + '';
var slicedLetter;
var sliceStart = -1;
var sliceEnd = -1;
var backwards = '';
for (i = characterTotal; i > 0; i--) {
if (i === characterTotal) { // first run
slicedLetter = inputStringStringified.slice(sliceStart);
sliceStart = sliceStart - 1;
backwards = backwards + slicedLetter;
} else {
slicedLetter = inputStringStringified.slice(sliceStart, sliceEnd);
sliceStart = sliceStart - 1;
sliceEnd = sliceEnd - 1;
backwards = backwards + slicedLetter;
}
}
if (inputString === backwards) {
alert('true');
} else {
alert('false');
}
}
checkPalindrome('abaaba');
I had to create an if statement for the first iteration. With atomrc's answer I see now I didn't have to use negative numbers for slice as I knew the strings length.
With new knowledge my new solution is a bit shorter..
function checkPalindrome(theString) {
var totalCharacters = theString.length;
var sliceStart = totalCharacters - 1;
var sliceEnd = totalCharacters;
var counter = totalCharacters;
var backwardsString = '';
while (counter > 0) {
var character = theString.slice(sliceStart, sliceEnd);
sliceStart--;
sliceEnd--;
counter--;
backwardsString = backwardsString + character;
}
if (theString === backwardsString) {
alert('its a palindrome');
} else {
alert('its not a palindrome');
}
}
checkPalindrome('abaaba');
..but still a terrible solution. After solving it I saw what others came up with and some are simple one liners.
There you go :)
theString.slice(theString.length - 1, theString.length)

How to use dynamic programming through Levenshtein algorithm (in Javascript)

I'm trying to understand dynamic programming through Levenshtein algorithm, but I have been stuck on this for a few hours now. I know my attempt at the following problem is the 'brute force' one. How would I use "dynamic programming" to change my approach? I'm pretty lost....
Problem: Given two strings, s and t, with lengths of n and m, create a
function that returns one of the following strings: "insert C" if
string t can be obtained from s by inserting character C "delete C"
(same logic as above) "swap c d" if string t can be obtained from
string s by swapping two adjacent characters (c and d) which appear in
that order in the original string. "Nothing" if no operation is
needed "impossible" if none of the above works ie LevenShtein distance is greater than 1.
Here is my brute force attempt. the "tuple" variable is misnamed as I originally wanted to push the indices and values to the matrix but got stuck on that.
function levenshtein(str1, str2) {
var m = str1.length,
n = str2.length,
d = [],
i, j,
vals = [],
vals2 = [];
for (i = 0; i <= m ; i++) {
var tuple = [str1[i]];
//console.log(tuple);
// console.log(tuple);
d[i] = [i];
// console.log(str1[i]);
vals.push(tuple);
}
vals = [].concat.apply([], vals);
vals = vals.filter(function(n){ return n; });
console.log(vals);
for (j = 0; j <= n; j++) {
d[0][j] = j;
var tuple2 = [str2[j]];
// console.log(tuple2);
vals2.push(tuple2);
// console.log(vals2);
}
vals2 = [].concat.apply([], vals2);
vals2 = vals2.filter(function(n){ return n ;});
console.log(vals2);
for (j = 1; j <= n; j++) {
for (i = 1; i <= m; i++) {
if (str1[i - 1] == str2[j - 1]) d[i][j] = d[i - 1][j - 1];
else d[i][j] = Math.min(d[i - 1][j], d[i][j - 1], d[i - 1][j - 1]) + 1;
}
}
var val = d[m][n];
// console.log(d);
if(val > 1){
return "IMPOSSIBLE";
}
if(val === 0){
return "NOTHING";
}
//console.log(d);
if(val === 1){
//find the missing element between the vals
//return "INSERT " + missing element
//find the extra element
//return "DELETE + " extra element
//find the out of place element and swap with another
}
}
console.log(levenshtein("kitten", "mitten"));
// console.log(levenshtein("stop", "tops"));
// console.log(levenshtein("blahblah", "blahblah"));
The problem as described cannot be optimized using dynamic programming because it only involves a single decision, not a series of decisions.
Note that the problem specifically states that you should return "impossible" when the Levenshtein distance is greater than 1, i.e., the strings can't be made equal through a single operation. You need to be searching for a sequence of zero or more operations that cumulatively result in the optimal solution if you want to apply dynamic programming. (This is what the dynamic programming wikipedia article is talking about when it says you need "optimal substructure" and "overlapping subproblems" for dynamic programming to be applicable.)
If you change the problem to calculate the full edit distance between two strings, then you can optimize using dynamic programming because you can reuse the result of choosing to do certain operations at a particular location in the string in order to reduce the complexity of the search.
Your current solution looks a bit overly complex for the given problem. Below a simpler approach you can study. This solution takes advantage of the fact that you know you can only do at most one operation, and you can infer which operation to attempt based off the difference between the lengths of the two strings. We also know that it only makes sense to try the given operation at the point where the two strings differ, rather than at every position.
function lev(s, t) {
// Strings are equal
if (s == t) return "nothing"
// Find difference in string lengths
var delta = s.length - t.length
// Explode strings into arrays
var arrS = s.split("")
var arrT = t.split("")
// Try swapping
if (delta == 0) {
for (var i=0; i<s.length; i++) {
if (arrS[i] != arrT[i]) {
var tmp = arrS[i]
arrS[i] = arrS[i+1]
arrS[i+1] = tmp
if (arrS.join("") == t) {
return "swap " + arrS[i+1] + " " + arrS[i]
}
else break
}
}
}
// Try deleting
else if (delta == 1) {
for (var i=0; i<s.length; i++) {
if (arrS[i] != arrT[i]) {
var c = arrS.splice(i, 1)[0]
if (arrS.join("") == t) {
return "delete " + c
}
else break
}
}
}
// Try inserting
else if (delta == -1) {
for (var i=0; i<t.length; i++) {
if (arrS[i] != arrT[i]) {
arrS.splice(i, 0, arrT[i])
if (arrS.join("") == t) {
return "insert " + arrS[i]
}
else break
}
}
}
// Strings are too different
return "impossible"
}
// output helper
function out(msg) { $("body").append($("<div/>").text(msg)) }
// tests
out(lev("kitten", "mitten"))
out(lev("kitten", "kitten"))
out(lev("kitten", "kitetn"))
out(lev("kiten", "kitten"))
out(lev("kitten", "kittn"))
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

how to check which elements of an array match relative to position

Trying to create a function or two that will be able to check the elements of an array and output wheater the elements of the two arrays are identical (ie same number and identical position is present), or the number is present but does not match the same position as the other array. Basically, I'm attempting to recreate a simple game called mastermind. The main problem im having is a case senarior when say the right answer is [1,2,3,4] and the user will guess [0,1,1,1], my function will out put that the number 1 is present 3 times, and I need to figure out how to just have it say the number 1 is present 1 time. Here is the function that checks the arrays:
function make_move(guess, answ){
var myguess = document.getElementById("mymoves");
var correct_number_correct_spot= 0;
var correct_number_wrong_spot= 0;
for(var i = 0; i < 4; ++i)
{
if(answ[i] == guess[i]){
++correct_number_correct_spot;
}
else if(answ[i] !== guess[i] && $.inArray(guess[i], answ) !== -1){
++correct_number_wrong_spot;
}
}
console.log(answ);
console.log(guess);
myguess.innerHTML += correct_number_correct_spot + " were right!" +correct_number_wrong_spot+ "there but not in the right order";
}
You can keep the count of missed numbers in an object, and subtract the guessed ones that appear in the answer. Then you can calculate the correct_number_wrong_spot subtracting the number of correct_number_correct_spot and the missed ones.
function make_move(guess, answ){
var myguess = document.getElementById("mymoves");
var correct_number_correct_spot = 0;
// Initialize missed counts to the numbers in the answer.
var correct_number_wrong_spot = answ.length;
var missed = {};
for (var j = 0; j < answ.length; j++) {
missed[answ[j]] = (missed[answ[j]] || 0) + 1;
}
for(var i = 0; i < answ.length; ++i)
{
if(answ[i] == guess[i]){
++correct_number_correct_spot;
}
// Subtract the guessed numbers from the missed counts.
if (guess[i] in missed) {
missed[guess[i]] = Math.max(0, missed[guess[i]] - 1);
}
}
// Subtract the correctly spotted numbers.
correct_number_wrong_spot -= correct_number_correct_spot;
// Subtract the remaining missed numbers.
for (var number in missed) {
correct_number_wrong_spot -= missed[number];
}
console.log(answ);
console.log(guess);
myguess.innerHTML += correct_number_correct_spot + " were right!" +correct_number_wrong_spot+ "there but not in the right order";
}
Check demo
EDIT: My try to explain doubts exposed in the comments:
would you mind explining how this code works: for (var j = 0; j < answ.length; j++) { missed[answ[j]] = (missed[answ[j]] || 0) + 1; }
missed[answ[j]] = (missed[answ[j]] || 0) + 1;
This is a quick way to increment the count for a number or initialize it to 0 if it doesn't exists yet. More or less the statement works like this:
If missed[answ[j]] is undefined then it is falsy and hence the || (or operator) evaluates to the 0. Otherwise, if we already have a value greater than 0, then it is truthy and the || evaluates to the contained number.
If it looks weird, you can replace this line with:
if (!(answ[j] in missed)) {
missed[answ[j]] = 0;
}
missed[answ[j]] += 1;
also if (guess[i] in missed) { missed[guess[i]] = Math.max(0, missed[guess[i]] - 1);
missed[guess[i]] = Math.max(0, missed[guess[i]] - 1);
In this case I use Math.max to make sure we don't subtract below 0. We don't want repeated numbers in the guess that exceeds the number of those present in the answer count. I mean, we subtract at most until the number of repeated numbers in the answer.
if (missed[guess[i]] > 0) {
missed[guess[i]] -= 1;
}
Try this fiddle!
Without changing your original function too much, you can use an object as a map to keep track of which numbers you have already matched.
var number_matched = {};
// ...
if(!number_matched[guess[i]]) {
number_matched[guess[i]] = true;
}

Algorithm of the greatest intersect of word in set of words

The story behind
I am creating a voice controlled application using x-webkit-speech which is surprisingly good (the feature, not my app), but sometimes the user (me) mumbles a bit. It would be nice to accept the command if some reasonable part of the word matches some reasonable part of some reasonable command. So I search for the holy grail called Algorithm of the Greatest Intersect of Word in Set of Words. Could some fresh bright mind drive me out of the cave of despair?
Example
"rotation" in ["notable","tattoo","onclick","statistically"]
should match tattoo because it has the longest intersect with rotation (tat_o). statistically is the second best (tati intersect), because longer part of the word needs to be ignored (but this is bonus condition, it would be acceptable without it).
Notes
I use Czech language where the pronunciation is very close to its written form
javascript is the preffered language, but any pseudocode is acceptable
the minimal length of the intersect should be a parameter of the algorithm
What have I tried?
Well, it is pretty embarassing....
for(var i=10; i>=4; --i) // reasonable substring
for(var word in words) // for all words in the set
for(var j=0; j<word.length-i; ++j) // search for any i substring
// aaargh... three levels of abstraction is too much for me
This is an algorithm that seems to work. I have no idea how good it performs compared to other already established algorithms (I suspect it perform worse) but maybe it gives you an idea how you could do it:
FIDDLE
var minInt = 3;
var arr = ["notable","tattoo","onclick","statistically"];
var word = "rotation";
var res = [];
if (word.length >= minInt) {
for (var i = 0; i < arr.length; i++) {
var comp = arr[i];
var m = 0;
if (comp.length >= minInt) {
for (var l = 0; l < comp.length - minInt + word.length - minInt + 1; l++) {
var subcomp = l > word.length - minInt ? comp.substring(l - word.length + minInt) : comp;
var subword = l < word.length - minInt ? word.substring(word.length - minInt - l) : word;
var minL = Math.min(subcomp.length, subword.length);
var matches = 0;
for (var k = 0; k < minL; k++) {
if (subcomp[k] === subword[k]) {
matches++;
}
}
if (matches > m) {
m = matches;
}
}
}
res[i] = m >= minInt ? m : null;
}
}
console.log(res);
What happens is, that it compares the two strings by "moving" on against the other and calculates the matching letters in each position. Here you see the compared "sub"words for rotation vs. notable:
ion / notable --> one match on index 1
tion / notable --> no match
ation / notable --> no match
tation / notable --> one match on index 2
otation / notable --> no match
rotation / notable --> three matches on index 1,2,3
rotation / otable --> no match
rotation / table --> no match
rotation / able --> no match
rotation / ble --> no match
As you see, the maximum number of matches is 3 and that is what it would return.
Here's an implementation of a Levenshtein Distance Calculator in Javascript.
It returns an object containing the matching command and distance.
var commandArr = ["cat", "dog", "fish", "copy", "delete"]
var testCommand = "bopy";
function closestMatch(str, arr)
{
//console.log("match called");
var matchDist = [];
var min, pos;
for(var i=0; i<arr.length; i++)
{
matchDist[i]=calcLevDist(str, arr[i]);
console.log("Testing "+ str + " against " + arr[i]);
}
//http://stackoverflow.com/questions/5442109/how-to-get-the-min-elements-inside-an-array-in-javascript
min = Math.min.apply(null,matchDist);
pos = matchDist.indexOf(min);
var output = { match : arr[pos],
distance : matchDist[pos]
};
return output;
}
function calcLevDist (str1, str2)
{
//console.log("calc running");
var cost = 0 , len1, len2;
var x = 1;
while(x > 0)
{
len1 = str1.length;
console.log("Length of String 1 = " + len1);
len2 = str2.length;
console.log("Length of String 2 = " + len2);
if(len1 == 0)
{
cost+= len2;
return cost;
}
if(len2 == 0)
{
cost+= len1;
return cost;
}
x = Math.min(len1,len2);
if(str1.charAt(len1 -1) != str2.charAt(len2 -1))
{
cost++;
}
else
console.log(str1.charAt(len1-1) + " matches " + str2.charAt(len2-1));
str1 = str1.substring(0, len1 -1 );
str2 = str2.substring(0, len2 -1 );
console.log("Current Cost = " + cost);
}
}
var matchObj = closestMatch(testCommand, commandArr);
var match = matchObj["match"];
var dist = matchObj["distance"];
$("#result").html("Closest match to " + testCommand + " = " + match + " with a Lev Distance of " + dist + "." )
You can mess around with the fiddle here.
Thank you basilikum and JasonNichols and also Mike and Andrew for the comments, it really helped me to finish the algorithm. I come up with my own brute force O(n^3) solution in case someone runs into this question with the same problem.
Anyone is invited to play with the fiddle to improve it.
The algorithm
/**
* Fuzzy match for word in array of strings with given accurancy
* #param string needle word to search
* #param int accurancy minimum matching characters
* #param array haystack array of strings to examine
* #return string matching word or undefined if none is found
*/
function fuzzyMatch(needle,accurancy,haystack) {
function strcmpshift(a,b,shift) {
var match=0, len=Math.min(a.length,b.length);
for(var i in a) if(a[i]==b[+i+shift]) ++match;
return match;
}
function strcmp(a,b) {
for(var i=0,max=0,now; i<b.length; ++i) {
now = strcmpshift(a,b,i);
if(now>max) max = now;
}
return max;
}
var word,best=accurancy-1,step,item;
for(var i in haystack) {
item = haystack[i];
step = Math.max(strcmp(item,needle),strcmp(needle,item));
if(step<=best) continue;
best=step, word=item;
};
return word;
}
Example
var word = "rotation";
var commands = ["notable","tattoo","onclick","statistically"];
// find the closest command with at least 3 matching characters
var command = fuzzyMatch(word,3,commands);
alert(command); // tattoo

Fast grouping of a javascript array

I have an array of a couple thousand strings
['7/21/2011', '7/21/2011', '7/21/2011', '7/20/2011', etc]
I am currently, running this code to group by the string and get the max group value:
var max = 0;
var group = {};
arr.map(function (value) {
if (group[value]) {
group[value]++;
} else {
group[value] = 1;
}
max = Math.max(max, group[value]);
});
Are there any improvements to make this code run faster?
EDIT:
The results are in: http://jsperf.com/javascript-array-grouping2
EDIT EDIT: that test was flawed. Mike Samuel's code was the fastest.
6000 entries test -> http://jsperf.com/javascript-array-grouping2
10K entries test -> http://jsperf.com/javascript-array-grouping
If you're sure this is a hotspot and speed is really important, I would try to cut out several thousand function calls by inlining max and map.
You can also make the body of your function faster by cutting out a comparison.
var max = 0;
var group = {};
for (var i = arr.length; --i >= 0;) {
var value = arr[i];
var n = group[value] = 1 - -(group[value] | 0);
if (n > max) { max = n; }
}
The best thing to do is measure on the browsers you care about.
Yes certainly. I would calculate the max last, instead of every iteration, and not use an if:
var group = {};
arr.map(function (value) {
group[value] = (group[value] || 0) + 1;
});
var max = 0;
for (key in group) {
if (group[key] > max) max = group[key];
}
EDIT: As Mike Samuel says you might get faster by using an index instead of map:
var group = {};
var max = 0;
for (var i = arr.length; --i >= 0;) {
group[value] = (group[value] || 0) + 1;
}
for (key in group) {
if (group[key] > max) max = group[key];
}
I think it really depends on the JS engine that you will run this code on. An alternative I think it's worth a shot is using
n = group[value] = (group[value]||0) + 1;
if (n > max) max = n;
for each element.
I also think that may be using a regular loop can be faster because the variables you will use will be just locals and not closed-over variables of a closure (that are normally slower) and you will also save a function call per element. Both those problems are non-issues if the implementation can inline this closure, but I don't know if there are JS implementations smart enough for that.

Categories

Resources