Damerau-Levenshtein distance Implementation - javascript

I'm trying to create a damerau-levenshtein distance function in JS.
I've found a description off the algorithm on WIkipedia, but they is no implementation off it. It says:
To devise a proper algorithm to calculate unrestricted
Damerau–Levenshtein distance note that there always exists an optimal
sequence of edit operations, where once-transposed letters are never
modified afterwards. Thus, we need to consider only two symmetric ways
of modifying a substring more than once: (1) transpose letters and
insert an arbitrary number of characters between them, or (2) delete a
sequence of characters and transpose letters that become adjacent
after deletion. The straightforward implementation of this idea gives
an algorithm of cubic complexity: O\left (M \cdot N \cdot \max(M, N)
\right ), where M and N are string lengths. Using the ideas of
Lowrance and Wagner,[7] this naive algorithm can be improved to be
O\left (M \cdot N \right) in the worst case. It is interesting that
the bitap algorithm can be modified to process transposition. See the
information retrieval section of[1] for an example of such an
adaptation.
https://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance
The section [1] points to http://acl.ldc.upenn.edu/P/P00/P00-1037.pdf which is even more complicated to me.
If I understood this correctly, it's not that easy to create an implementation off it.
Here's the levenshtein implementation I currently use :
levenshtein=function (s1, s2) {
// discuss at: http://phpjs.org/functions/levenshtein/
// original by: Carlos R. L. Rodrigues (http://www.jsfromhell.com)
// bugfixed by: Onno Marsman
// revised by: Andrea Giammarchi (http://webreflection.blogspot.com)
// reimplemented by: Brett Zamir (http://brett-zamir.me)
// reimplemented by: Alexander M Beedie
// example 1: levenshtein('Kevin van Zonneveld', 'Kevin van Sommeveld');
// returns 1: 3
if (s1 == s2) {
return 0;
}
var s1_len = s1.length;
var s2_len = s2.length;
if (s1_len === 0) {
return s2_len;
}
if (s2_len === 0) {
return s1_len;
}
// BEGIN STATIC
var split = false;
try {
split = !('0')[0];
} catch (e) {
// Earlier IE may not support access by string index
split = true;
}
// END STATIC
if (split) {
s1 = s1.split('');
s2 = s2.split('');
}
var v0 = new Array(s1_len + 1);
var v1 = new Array(s1_len + 1);
var s1_idx = 0,
s2_idx = 0,
cost = 0;
for (s1_idx = 0; s1_idx < s1_len + 1; s1_idx++) {
v0[s1_idx] = s1_idx;
}
var char_s1 = '',
char_s2 = '';
for (s2_idx = 1; s2_idx <= s2_len; s2_idx++) {
v1[0] = s2_idx;
char_s2 = s2[s2_idx - 1];
for (s1_idx = 0; s1_idx < s1_len; s1_idx++) {
char_s1 = s1[s1_idx];
cost = (char_s1 == char_s2) ? 0 : 1;
var m_min = v0[s1_idx + 1] + 1;
var b = v1[s1_idx] + 1;
var c = v0[s1_idx] + cost;
if (b < m_min) {
m_min = b;
}
if (c < m_min) {
m_min = c;
}
v1[s1_idx + 1] = m_min;
}
var v_tmp = v0;
v0 = v1;
v1 = v_tmp;
}
return v0[s1_len];
}
What are your ideas for building such an algorithm and, if you think it would be too complicated, what could I do to make no difference between 'l' (L lowercase) and 'I' (i uppercase) for example.

The gist #doukremt gave: https://gist.github.com/doukremt/9473228
gives the following in Javascript.
You can change the weights of operations in the weighter object.
var levenshteinWeighted= function(seq1,seq2)
{
var len1=seq1.length;
var len2=seq2.length;
var i, j;
var dist;
var ic, dc, rc;
var last, old, column;
var weighter={
insert:function(c) { return 1.; },
delete:function(c) { return 0.5; },
replace:function(c, d) { return 0.3; }
};
/* don't swap the sequences, or this is gonna be painful */
if (len1 == 0 || len2 == 0) {
dist = 0;
while (len1)
dist += weighter.delete(seq1[--len1]);
while (len2)
dist += weighter.insert(seq2[--len2]);
return dist;
}
column = []; // malloc((len2 + 1) * sizeof(double));
//if (!column) return -1;
column[0] = 0;
for (j = 1; j <= len2; ++j)
column[j] = column[j - 1] + weighter.insert(seq2[j - 1]);
for (i = 1; i <= len1; ++i) {
last = column[0]; /* m[i-1][0] */
column[0] += weighter.delete(seq1[i - 1]); /* m[i][0] */
for (j = 1; j <= len2; ++j) {
old = column[j];
if (seq1[i - 1] == seq2[j - 1]) {
column[j] = last; /* m[i-1][j-1] */
} else {
ic = column[j - 1] + weighter.insert(seq2[j - 1]); /* m[i][j-1] */
dc = column[j] + weighter.delete(seq1[i - 1]); /* m[i-1][j] */
rc = last + weighter.replace(seq1[i - 1], seq2[j - 1]); /* m[i-1][j-1] */
column[j] = ic < dc ? ic : (dc < rc ? dc : rc);
}
last = old;
}
}
dist = column[len2];
return dist;
}

Stolen from here, with formatting and some examples on how to use it:
function DamerauLevenshtein(prices, damerau) {
//'prices' customisation of the edit costs by passing an object with optional 'insert', 'remove', 'substitute', and
//'transpose' keys, corresponding to either a constant number, or a function that returns the cost. The default cost
//for each operation is 1. The price functions take relevant character(s) as arguments, should return numbers, and
//have the following form:
//
//insert: function (inserted) { return NUMBER; }
//
//remove: function (removed) { return NUMBER; }
//
//substitute: function (from, to) { return NUMBER; }
//
//transpose: function (backward, forward) { return NUMBER; }
//
//The damerau flag allows us to turn off transposition and only do plain Levenshtein distance.
if (damerau !== false) {
damerau = true;
}
if (!prices) {
prices = {};
}
let insert, remove, substitute, transpose;
switch (typeof prices.insert) {
case 'function':
insert = prices.insert;
break;
case 'number':
insert = function (c) {
return prices.insert;
};
break;
default:
insert = function (c) {
return 1;
};
break;
}
switch (typeof prices.remove) {
case 'function':
remove = prices.remove;
break;
case 'number':
remove = function (c) {
return prices.remove;
};
break;
default:
remove = function (c) {
return 1;
};
break;
}
switch (typeof prices.substitute) {
case 'function':
substitute = prices.substitute;
break;
case 'number':
substitute = function (from, to) {
return prices.substitute;
};
break;
default:
substitute = function (from, to) {
return 1;
};
break;
}
switch (typeof prices.transpose) {
case 'function':
transpose = prices.transpose;
break;
case 'number':
transpose = function (backward, forward) {
return prices.transpose;
};
break;
default:
transpose = function (backward, forward) {
return 1;
};
break;
}
function distance(down, across) {
//http://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance
let ds = [];
if (down === across) {
return 0;
} else {
down = down.split('');
down.unshift(null);
across = across.split('');
across.unshift(null);
down.forEach(function (d, i) {
if (!ds[i]) {
ds[i] = [];
}
across.forEach(function (a, j) {
if (i === 0 && j === 0) {
ds[i][j] = 0;
} else if (i === 0) {
//Empty down (i == 0) -> across[1..j] by inserting
ds[i][j] = ds[i][j - 1] + insert(a);
} else if (j === 0) {
//Down -> empty across (j == 0) by deleting
ds[i][j] = ds[i - 1][j] + remove(d);
} else {
//Find the least costly operation that turns the prefix down[1..i] into the prefix across[1..j] using
//already calculated costs for getting to shorter matches.
ds[i][j] = Math.min(
//Cost of editing down[1..i-1] to across[1..j] plus cost of deleting
//down[i] to get to down[1..i-1].
ds[i - 1][j] + remove(d),
//Cost of editing down[1..i] to across[1..j-1] plus cost of inserting across[j] to get to across[1..j].
ds[i][j - 1] + insert(a),
//Cost of editing down[1..i-1] to across[1..j-1] plus cost of substituting down[i] (d) with across[j]
//(a) to get to across[1..j].
ds[i - 1][j - 1] + (d === a ? 0 : substitute(d, a))
);
//Can we match the last two letters of down with across by transposing them? Cost of getting from
//down[i-2] to across[j-2] plus cost of moving down[i-1] forward and down[i] backward to match
//across[j-1..j].
if (damerau && i > 1 && j > 1 && down[i - 1] === a && d === across[j - 1]) {
ds[i][j] = Math.min(ds[i][j], ds[i - 2][j - 2] + (d === a ? 0 : transpose(d, down[i - 1])));
}
}
});
});
return ds[down.length - 1][across.length - 1];
}
}
return distance;
}
//Returns a distance function to call.
let dl = DamerauLevenshtein();
console.log(dl('12345', '23451'));
console.log(dl('this is a test', 'this is not a test'));
console.log(dl('testing testing 123', 'test'));

Related

Make an operation based on string, including priority for multiplication and division with vanilla javascript and nodejs [duplicate]

This question already has answers here:
Calculate string value in javascript, not using eval
(12 answers)
Closed 5 months ago.
I am trying to do a specific algorithm with Javascript, and... it's a little bit tricky. Here is what i am trying to do :
a console app (with nodejs and "readline"). I ask the user to make an operation, example :
"2 + 3 * 4 / 2 - 1"
what I want to do, is to read the string, turn it into an array, identify the priority operator (* and /), and then, replace (without regex) the operation at the place where previously there was the operation, and so on. With my example, it should do :
[2 + 12 / 2 - 1] => [2 + 6 - 1] => [8 - 1] => [7].
So far, i succeeded in making an app that can add with many operators, but I got 2 problems :
1/ I dont know how to make the priority
2/ my app does not support more than 3 operations...
Here is my code, I appreciate any helps to undrstand what I am doing wrong and any help to finally end this algo. Thank you :
const readline = require("readline-sync");
const operators = ["+", "-", "*", "/"];
const openParenthesis = ['(', '[', '{'];
const closeParenthesis = [')', ']', '}'];
var numericOperator = 0;
var operatorsList = []
var operatorArray = [];
var NumberA = 0;
var NumberB = 0;
var Total = 0;
var soloOperator = "";
var removeValFromIndex = [];
var indexOperator = [];
var opFound = "";
function askOperator() {
operator = readline.question(`make an operation: `)
operatorArray = operator.split(' ');
console.log(operatorArray, operatorArray.length)
}
askOperator();
splitArray(operatorArray);
function splitArray(sentenceArray) {
for (let i = 0; i < sentenceArray.length; i++) {
opFound = operators.find(el => el == sentenceArray[i]);
if(opFound == "*") {
const findMultiplyer = (element) => element == opFound;
indexOperator = sentenceArray.findIndex(findMultiplyer);
soloOperator = sentenceArray[indexOperator];
NumberA = sentenceArray[indexOperator - 1];
NumberB = sentenceArray[indexOperator + 1];
removeValFromIndex.push((indexOperator - 1), indexOperator, (indexOperator + 1));
for (var j = removeValFromIndex.length -1; j >= 0; j--){
sentenceArray.splice(removeValFromIndex[j],1);
}
} else if (opFound == "/") {
const findDivider = (element) => element == opFound;
indexOperator = sentenceArray.findIndex(findDivider);
soloOperator = sentenceArray[indexOperator];
NumberA = sentenceArray[indexOperator - 1];
NumberB = sentenceArray[indexOperator + 1];
removeValFromIndex.push((indexOperator - 1), indexOperator, (indexOperator + 1));
for (var j = removeValFromIndex.length -1; j >= 0; j--){
sentenceArray.splice(removeValFromIndex[j],1);
}
} else if (opFound == "+") {
const findAdd = (element) => element == opFound;
indexOperator = sentenceArray.findIndex(findAdd);
soloOperator = sentenceArray[indexOperator];
NumberA = sentenceArray[indexOperator - 1];
NumberB = sentenceArray[indexOperator + 1];
removeValFromIndex.push((indexOperator - 1), indexOperator, (indexOperator + 1));
for (var j = removeValFromIndex.length -1; j >= 0; j--){
sentenceArray.splice(removeValFromIndex[j],1);
}
} else if (opFound == "-") {
const findMinus = (element) => element == opFound;
indexOperator = sentenceArray.findIndex(findMinus);
soloOperator = sentenceArray[indexOperator];
NumberA = sentenceArray[indexOperator - 1];
NumberB = sentenceArray[indexOperator + 1];
removeValFromIndex.push((indexOperator - 1), indexOperator, (indexOperator + 1));
for (var j = removeValFromIndex.length -1; j >= 0; j--){
sentenceArray.splice(removeValFromIndex[j],1);
}
}
console.log("loop", opFound, "la", removeValFromIndex ,sentenceArray)
}
console.log("test", indexOperator, "other", soloOperator, NumberA, NumberB);
doMath(NumberA, NumberB)
}
function doMath(numA, numB) {
console.log("index in math", indexOperator)
switch (soloOperator) {
case '+' :
Total = (parseInt(numA) + parseInt(numB));
// operatorArray trouver * ou / si cest le cas on saute cette section
if (indexOperator > 1) {
operatorArray.splice((indexOperator), 0, Total.toString())
} else {
operatorArray.splice((indexOperator -1), 0, Total.toString())
}
if (operatorArray.length >= 3) {
return splitArray(operatorArray)
}
console.log("addition", Total, "new array", operatorArray );
break;
case '-' :
Total = numA - numB;
if (indexOperator > 1) {
operatorArray.splice((indexOperator), 0, Total.toString())
} else {
operatorArray.splice((indexOperator -1), 0, Total.toString())
}
if (operatorArray.length >= 3) {
return splitArray(operatorArray)
}
console.log("substract", Total, "new array", operatorArray);
break;
case '*' :
Total = numA * numB;
if (indexOperator > 1) {
operatorArray.splice((indexOperator), 0, Total.toString())
} else {
operatorArray.splice((indexOperator -1), 0, Total.toString())
}
if (operatorArray.length >= 3) {
return splitArray(operatorArray)
}
console.log(indexOperator,"multiply", Total, "new array", operatorArray);
break;
case '/' :
Total = numA / numB;
if (indexOperator > 1) {
operatorArray.splice((indexOperator), 0, Total.toString())
} else {
operatorArray.splice((indexOperator -1), 0, Total.toString())
}
if (operatorArray.length >= 3) {
return splitArray(operatorArray)
}
operatorArray.splice((indexOperator), 0, Total.toString())
console.log("divide", Total, "new array", operatorArray);
break;
default:
console.log("An error occured")
break;
}
}
so, I found a working solution, here it is, but still, there is a regex inside, is there any way to replace it by something else?
const readline = require("readline-sync");
var sentence = "";
function askOperator() {
sentence = readline.question(`make an operation: `);
}
askOperator();
function MathSolver() {
this.infixToPostfix = function (infix) {
var outputQueue = "";
var operatorStack = [];
var operators = {
"^": {
precedence: 4,
associativity: "Right"
},
"/": {
precedence: 3,
associativity: "Left"
},
"*": {
precedence: 3,
associativity: "Left"
},
// <<<< test
"%": {
precedence: 3,
associativity: "Left"
},
// >>>>
"+": {
precedence: 2,
associativity: "Left"
},
"-": {
precedence: 2,
associativity: "Left"
}
};
infix = infix.replace(/\s+/g, "");
infix = infix.split(/([\+\-\*\%\/\^\(\)])/).clean();
for (var i = 0; i < infix.length; i++) {
var token = infix[i];
console.log("token",token)
if (isNumeric(token)) {
outputQueue += token + " ";
} else if ("^*%/+-".indexOf(token) !== -1) {
var o1 = token;
var o2 = operatorStack[operatorStack.length - 1];
while (
"^*%/+-".indexOf(o2) !== -1 &&
((operators[o1].associativity === "Left" &&
operators[o1].precedence <= operators[o2].precedence) ||
(operators[o1].associativity === "Right" &&
operators[o1].precedence < operators[o2].precedence))
) {
outputQueue += operatorStack.pop() + " ";
o2 = operatorStack[operatorStack.length - 1];
}
operatorStack.push(o1);
} else if (token === "(") {
operatorStack.push(token);
} else if (token === ")") {
while (operatorStack[operatorStack.length - 1] !== "(") {
outputQueue += operatorStack.pop() + " ";
}
operatorStack.pop();
}
}
while (operatorStack.length > 0) {
outputQueue += operatorStack.pop() + " ";
}
return outputQueue;
};
}
let ms = new MathSolver();
console.log("ms>>", ms.infixToPostfix("10 - 15 * 20"));
const e = ms.infixToPostfix(sentence);
console.log("e>", e);
const s = [],
tokens = e.split(" ");
for (const t of tokens) {
const n = Number(t);
if (!isNaN(n)) {
s.push(n);
} else {
if (s.length < 2) {
throw new Error(`${t}: ${s}: insufficient operands.`);
}
const o2 = s.pop(),
o1 = s.pop();
switch (t) {
case "+":
s.push(o1 + o2);
break;
case "-":
s.push(o1 - o2);
break;
case "*":
s.push(o1 * o2);
break;
case "%":
s.push(o1, (o1 / 100) * o2);
break;
case "/":
s.push(o1 / o2);
break;
case "^":
s.push(Math.pow(o1, o2));
break;
default:
throw new Error(`Unrecognized operator: [${t}]`);
}
}
console.log(`${t}: ${s}`);
}
function isNumeric(num) {
return !isNaN(parseFloat(num)) && isFinite(num);
}

How to generate a random number based on fixed character set in javascript

I'm generating a number based on a fixed character set.
function generator()
{
var text = "";
var char_list = "LEDGJR", number_list = "0123456789";
for(var i=0; i < 2; i++ )
{
text += char_list.charAt(Math.floor(Math.random() * char_list.length));
}
for(var j=0; j < 2; j++ )
{
text += number_list.charAt(Math.floor(Math.random() *
number_list.length));
}
return text;
}
Result :
RE39, JE12 etc...
Once all the permutation related to the above sequence is done, then the generator should generate string as RE391, JE125 means adding one more number to the complete number.
How can I get the permutation count of sequence?
For simplicity consider the case where:
chars = "AB"
nums = "123";
and we want to generate a 4-digit sequence of two chars and two numbers.
We define these variables
rows = [chars, chars, nums, nums]
rowSizes = rows.map(row => row.length) // [2, 2, 3, 3]
It’s easy to see the set size of all possible permuations equals:
spaceSize = rowSizes.reduce((m, n) => m * n, 1) // 2*2*3*3 = 36
And we define two set of utility functions, usage of which I'll explain in detail later.
decodeIndex() which gives us uniqueness
function euclideanDivision(a, b) {
const remainder = a % b;
const quotient = (a - remainder) / b;
return [quotient, remainder]
}
function decodeIndex(index, rowSizes) {
const rowIndexes = []
let dividend = index
for (let i = 0; i < rowSizes.length; i++) {
const [quotient, remainder] = euclideanDivision(dividend, rowSizes[i])
rowIndexes[i] = remainder
dividend = quotient
}
return rowIndexes
}
getNextIndex() which gives us pseudo-randomness
function isPrime(n) {
if (n <= 1) return false;
if (n <= 3) return true;
if (n % 2 == 0 || n % 3 == 0) return false;
for (let i = 5; i * i <= n; i = i + 6) {
if (n % i == 0 || n % (i + 2) == 0) return false;
}
return true;
}
function findNextPrime(n) {
if (n <= 1) return 2;
let prime = n;
while (true) {
prime++;
if (isPrime(prime)) return prime;
}
}
function getIndexGeneratorParams(spaceSize) {
const N = spaceSize;
const Q = findNextPrime(Math.floor(2 * N / (1 + Math.sqrt(5))))
const firstIndex = Math.floor(Math.random() * spaceSize);
return [firstIndex, N, Q]
}
function getNextIndex(prevIndex, N, Q) {
return (prevIndex + Q) % N
}
Uniqueness
Like mentioned above, spaceSize is the number of all possible permutations, thus each index in range(0, spaceSize) uniquely maps to one permutation. decodeIndex helps with this mapping, you can get the corresponding permutation to an index by:
function getSequenceAtIndex(index) {
const tuple = decodeIndex(index, rowSizes)
return rows.map((row, i) => row[tuple[i]]).join('')
}
Pseudo-Randomness
(Credit to this question. I just port that code into JS.)
We get pseudo-randomness by polling a "full cycle iterator"†. The idea is simple:
have the indexes 0..35 layout in a circle, denote upperbound as N=36
decide a step size, denoted as Q (Q=23 in this case) given by this formula‡
Q = findNextPrime(Math.floor(2 * N / (1 + Math.sqrt(5))))
randomly decide a starting point, e.g. number 5
start generating seemingly random nextIndex from prevIndex, by
nextIndex = (prevIndex + Q) % N
So if we put 5 in we get (5 + 23) % 36 == 28. Put 28 in we get (28 + 23) % 36 == 15.
This process will go through every number in circle (jump back and forth among points on the circle), it will pick each number only once, without repeating. When we get back to our starting point 5, we know we've reach the end.
†: I'm not sure about this term, just quoting from this answer
‡: This formula only gives a nice step size that will make things look more "random", the only requirement for Q is it must be coprime to N
Full Solution
Now let's put all the pieces together. Run the snippet to see result.
I've also includes the a counter before each log. For your case with char_list="LEDGJR", number_list="0123456789", the spaceSize for 4-digit sequence should be 6*6*10*10 = 3600
You'll observe the log bump to 5-digit sequence at 3601 😉
function euclideanDivision(a, b) {
const remainder = a % b;
const quotient = (a - remainder) / b;
return [quotient, remainder];
}
function decodeIndex(index, rowSizes) {
const rowIndexes = [];
let divident = index;
for (let i = 0; i < rowSizes.length; i++) {
const [quotient, remainder] = euclideanDivision(divident, rowSizes[i]);
rowIndexes[i] = remainder;
divident = quotient;
}
return rowIndexes;
}
function isPrime(n) {
if (n <= 1) return false;
if (n <= 3) return true;
if (n % 2 == 0 || n % 3 == 0) return false;
for (let i = 5; i * i <= n; i = i + 6) {
if (n % i == 0 || n % (i + 2) == 0) return false;
}
return true;
}
function findNextPrime(n) {
if (n <= 1) return 2;
let prime = n;
while (true) {
prime++;
if (isPrime(prime)) return prime;
}
}
function getIndexGeneratorParams(spaceSize) {
const N = spaceSize;
const Q = findNextPrime(Math.floor((2 * N) / (1 + Math.sqrt(5))));
const firstIndex = Math.floor(Math.random() * spaceSize);
return [firstIndex, N, Q];
}
function getNextIndex(prevIndex, N, Q) {
return (prevIndex + Q) % N;
}
function generatorFactory(rows) {
const rowSizes = rows.map((row) => row.length);
function getSequenceAtIndex(index) {
const tuple = decodeIndex(index, rowSizes);
return rows.map((row, i) => row[tuple[i]]).join("");
}
const spaceSize = rowSizes.reduce((m, n) => m * n, 1);
const [firstIndex, N, Q] = getIndexGeneratorParams(spaceSize);
let currentIndex = firstIndex;
let exhausted = false;
function generator() {
if (exhausted) return null;
const sequence = getSequenceAtIndex(currentIndex);
currentIndex = getNextIndex(currentIndex, N, Q);
if (currentIndex === firstIndex) exhausted = true;
return sequence;
}
return generator;
}
function getRows(chars, nums, rowsOfChars, rowsOfNums) {
const rows = [];
while (rowsOfChars--) {
rows.push(chars);
}
while (rowsOfNums--) {
rows.push(nums);
}
return rows;
}
function autoRenewGeneratorFactory(chars, nums, initRowsOfChars, initRowsOfNums) {
let realGenerator;
let currentRowOfNums = initRowsOfNums;
function createRealGenerator() {
const rows = getRows(chars, nums, initRowsOfChars, currentRowOfNums);
const generator = generatorFactory(rows);
currentRowOfNums++;
return generator;
}
realGenerator = createRealGenerator();
function proxyGenerator() {
const sequence = realGenerator();
if (sequence === null) {
realGenerator = createRealGenerator();
return realGenerator();
} else {
return sequence;
}
}
return proxyGenerator;
}
function main() {
const char_list = "LEDGJR"
const number_list = "0123456789";
const generator = autoRenewGeneratorFactory(char_list, number_list, 2, 2);
let couter = 0
setInterval(() => {
console.log(++couter, generator())
}, 10);
}
main();

Min heap in javascript

Hey I tried to implement a min heap in javascript, but i had a question regarding the algorithm for remove min. I'm using an array to represent the heap internally. When I'm percolating downwards, what should be the stopping condition? In my code I have it at 2 * k <= this.size so it would travel down to potentially the very last element, but it doesn't feel "correct", is there a better stopping condition? Thanks in advance!
this.removeMin = function () {
//replace root with last element and percolate downwards
var min = this._heap[1],
k,
left,
right;
this._heap[1] = this._heap.pop();
this.size--;
k = 1;
while ( ( 2 * k ) <= this.size) {
left = 2 * k;
right = 2 * k + 1;
if (this._heap[k] > this._heap[left] && this._heap[k] > this._heap[right]) {
if (this._heap[left] <= this._heap[right]) {
swap(this._heap, k, left);
k = left;
} else {
swap(this._heap, k, right);
k = right;
}
} else if (this._heap[k] > this._heap[left]) {
swap(this._heap, k, left);
k = left;
} else {
swap(this._heap, k, right);
k = right;
}
}
return min;
};
This is a much easier implementation, I hope this can help.
class MinHeap {
constructor(array) {
this.heap = this.buildHeap(array);
}
// O(n) time | O(1) space
buildHeap(array) {
const firstParentIdx = Math.floor((array.length - 2) / 2);
for (let currentIdx = firstParentIdx; currentIdx >= 0; currentIdx--) {
this.siftDown(currentIdx, array.length - 1, array);
}
return array;
}
// O(log(n)) time | O(1) space
siftDown(currentIdx, endIdx, heap) {
let childOneIdx = currentIdx * 2 + 1;
while (childOneIdx <= endIdx) {
const childTwoIdx = currentIdx * 2 + 2 <= endIdx ? currentIdx * 2 + 2 : -1;
let idxToSwap;
if (childTwoIdx !== -1 && heap[childTwoIdx] < heap[childOneIdx]) {
idxToSwap = childTwoIdx;
} else {
idxToSwap = childOneIdx;
}
if (heap[idxToSwap] < heap[currentIdx]) {
this.swap(currentIdx, idxToSwap, heap);
currentIdx = idxToSwap;
childOneIdx = currentIdx * 2 + 1;
} else {
return;
}
}
}
// O(log(n)) time | O(1) space
siftUp(currentIdx, heap) {
let parentIdx = Math.floor((currentIdx - 1) / 2);
while (currentIdx > 0 && heap[currentIdx] < heap[parentIdx]) {
this.swap(currentIdx, parentIdx, heap);
currentIdx = parentIdx;
parentIdx = Math.floor((currentIdx - 1) / 2);
}
}
// O(1) time | O(1) space
peek() {
return this.heap[0];
}
// O(log(n)) time | O(1) space
remove() {
this.swap(0, this.heap.length - 1, this.heap);
const valueToRemove = this.heap.pop();
this.siftDown(0, this.heap.length - 1, this.heap);
return valueToRemove;
}
// O(log(n)) time | O(1) space
insert(value) {
this.heap.push(value);
this.siftUp(this.heap.length - 1, this.heap);
}
swap(i, j, heap) {
[heap[i], heap[j]] = [heap[j], heap[i]];
}
}
I think you miss one if condition. When the k element is both less than the right and the left, the downwards must stop.
It must be:
if (this._heap[k] > this._heap[left] && this._heap[k] > this._heap[right]) {
if (this._heap[left] <= this._heap[right]) {
swap(this._heap, k, left);
k = left;
} else {
swap(this._heap, k, right);
k = right;
}
} else if (this._heap[k] > this._heap[left]) {
swap(this._heap, k, left);
k = left;
} else if(this._heap[k] < this._heap[right]) {
swap(this._heap, k, right);
k = right;
}else{
break;
}
Why you are writing compare code again and again. Sharing below code for reference which will optimize your code, you can replace it with your while loop.
.....
while (2 * k <= this.size) {
let j = 2 * key;
if (j < this.size && less(j, j + 1)) j++; // find smallest child
if (!less(k, j)) break; // check parent is lesser than smallest child or not
exch(k, j); //if parent is bigger then exchange
k = j; //keep checking untill reaches to the end(this.size)
}
.....
function less(i, j){
if(this._heap[j] < this._heap[i] < 0) return true;
else return false;
}
function exch(i, j){
let temp = this._heap[i];
this._heap[i] = this._heap[j];
this._heap[j] = temp;
}
Hope this works.

Javascript toFixed function

The expected result of:
(1.175).toFixed(2) = 1.18 and
(5.175).toFixed(2) = 5.18
But in JS showing:
(1.175).toFixed(2) = 1.18 but
*(5.175).toFixed(2) = 5.17*
How to rectify the problem?
It's not a bug. It's related to the fact numbers aren't stored in decimal but in IEEE754 (so 5.175 isn't exactly stored).
If you want to round in a specific direction (up) and you consistently have numbers of this precision, you might use this trick :
(5.175 + 0.00001).toFixed(2)
You could always try using round, instead of toFixed.
Math.round(5.175*100)/100
You could even try putting it in some prototype method if you want.
Created a jsBin that implements a simple prototype on Number.
Number.prototype.toFixed = function(decimals) {
return Math.round(this * Math.pow(10, decimals)) / (Math.pow(10, decimals));
};
It's because the numbers are stored as IEEE754.
I would recommend you to use the Math class (round, floor or ceil methods, depending on your needing).
I've just created a class MathHelper that can easily solve your problem:
var MathHelper = (function () {
this.round = function (number, numberOfDecimals) {
var aux = Math.pow(10, numberOfDecimals);
return Math.round(number * aux) / aux;
};
this.floor = function (number, numberOfDecimals) {
var aux = Math.pow(10, numberOfDecimals);
return Math.floor(number * aux) / aux;
};
this.ceil = function (number, numberOfDecimals) {
var aux = Math.pow(10, numberOfDecimals);
return Math.ceil(number * aux) / aux;
};
return {
round: round,
floor: floor,
ceil: ceil
}
})();
Usage:
MathHelper.round(5.175, 2)
Demo: http://jsfiddle.net/v2Dj7/
Actually I think this is a bug in the implementation of Number.prototype.toFixed. The algorithm given in ECMA-262 20.1.3.3 10-a says to round up as a tie-breaker. As others have mentioned, there probably isn't a tie to break due to floating point imprecisions in the implementation. But that doesn't make it right :)
At least this behavior is consistent across FF, Chrome, Opera, Safari. (Haven't tried others.)
FWIW, you can actually implement your own version of toFixed per the spec in JS and that behaves as you would expect. See http://jsfiddle.net/joenudell/7qahrb6d/.
Kippie
your solution has problems
one of them
39133.005.toFixed(2) => 39133
var Calc = function () {
var self = this;
this.float2Array = function(num) {
var floatString = num.toString(),
exp = floatString.indexOf(".") - (floatString.length - 1),
mant = floatString.replace(".", "").split("").map(function (i) { return parseInt(i); });
return { exp: exp, mant: mant };
};
this.round2 = function (num, dec, sep) {
var decimal = !!dec ? dec : 2,
separator = !!sep ? sep : '',
r = parseFloat(num),
exp10 = Math.pow(10, decimal);
r = Math.round(r * exp10) / exp10;
var rr = Number(r).toFixed(decimal).toString().split('.');
var b = rr[0].replace(/(\d{1,3}(?=(\d{3})+(?:\.\d|\b)))/g, "\$1" + separator);
r = (rr[1] ? b + '.' + rr[1] : b);
return r;
};
this.toFixed10 = function (f, num) {
var prepareInt = self.float2Array(f),
naturalInt = prepareInt.mant,
places = Math.abs(prepareInt.exp),
result = prepareInt.mant.slice(),
resultFixedLenth;
// if number non fractional or has zero fractional part
if (f.isInt()) {
return f.toFixed(num);
}
// if the number of decimal places equals to required rounding
if (places === num) {
return Number(f).toString();
}
//if number has trailing zero (when casting to string type float 1.0050 => "1.005" => 005 <0050)
if (places < num) {
return Number(f).round2(num);
}
for (var e = naturalInt.length - (places > num ? (places - num) : 0), s = 0; e >= s; e--) {
if (naturalInt.hasOwnProperty(e)) {
if (naturalInt[e] > 4 && naturalInt[e - 1] < 9) {
result[e] = 0;
result[e - 1] = naturalInt[e - 1] + 1;
break;
} else if (naturalInt[e] > 4 && naturalInt[e - 1] === 9) {
result[e] = 0;
result[e - 1] = 0;
result[e - 2] = naturalInt[e - 2] < 9 ? naturalInt[e - 2] + 1 : 0;
} else if (e === 0 && naturalInt[e] === 9 && naturalInt[e + 1] === 9) {
result[e] = 0;
result.unshift(1);
} else {
break;
}
}
}
if (places - num > 0) {
resultFixedLenth = result.slice(0, -(places - num));
} else {
for (var i = 0, l = num - places; i < l; i++) {
result.push(0);
}
resultFixedLenth = result;
}
return (parseInt(resultFixedLenth.join("")) / Math.pow(10, num)).round2(num);
};
this.polyfill = function() {
if (!Array.prototype.map) {
Array.prototype.map = function (callback, thisArg) {
var T, A, k;
if (this == null) { throw new TypeError(' this is null or not defined'); }
var O = Object(this), len = O.length >>> 0;
if (typeof callback !== 'function') { throw new TypeError(callback + ' is not a function'); }
if (arguments.length > 1) { T = thisArg; }
A = new Array(len);
k = 0;
while (k < len) {
var kValue, mappedValue;
if (k in O) {
kValue = O[k];
mappedValue = callback.call(T, kValue, k, O);
A[k] = mappedValue;
}
k++;
}
return A;
};
}
};
this.init = function () {
self.polyfill();
Number.prototype.toFixed10 = function (decimal) {
return calc.toFixed10(this, decimal);
}
Number.prototype.round2 = function (decimal) {
return calc.round2(this, decimal);
}
Number.prototype.isInt = function () {
return (Math.round(this) == this);
}
}
}, calc = new Calc(); calc.init();
this works good)
obj = {
round(val) {
const delta = 0.00001
let num = val
if (num - Math.floor(num) === 0.5) {
num += delta
}
return Math.round(num + delta)
},
fixed(val, count = 0) {
const power = Math.pow(10, count)
let res = this.round(val * power) / power
let arr = `${res}`.split('.')
let addZero = ''
for (let i = 0; i < count; i++) {
addZero += '0'
}
if (count > 0) {
arr[1] = ((arr[1] || '') + addZero).substr(0, count)
}
return arr.join('.')
}
}
obj.fixed(5.175, 2)
// "5.18"

Convert integer to alpha ordered list equivalent

I need to a function to convert an integer to the equivalent alpha ordered list index. For example:
1 = a
2 = b
.
.
.
26 = z
27 = aa
28 = ab
.
.
etc.
Currently I have the following which almost works but there's a small logic error somewhere that makes it not quite get it right (it goes ax, ay, bz, ba, bb, bc...):
function intToAlpha( int ) {
var asciiStart = 97,
alphaMax = 26,
asciiCode,
char,
alpha = '',
place,
num,
i;
for ( i = 0; Math.pow(alphaMax, i) < int; i++ ) {
place = Math.pow(alphaMax, i);
num = Math.floor( ( int / place ) % alphaMax);
asciiCode = ( num == 0 ? alphaMax : num ) + asciiStart - 1;
char = String.fromCharCode(asciiCode);
alpha = char + alpha;
}
return alpha;
}
for (i = 1; i < 300; i++) {
console.log( i + ': ' + intToAlpha(i) );
}
This function is used in NVu/Kompozer/SeaMonkey Composer, with a small tweak to generate lower case directly:
function ConvertArabicToLetters(num)
{
var letters = "";
while (num > 0) {
num--;
letters = String.fromCharCode(97 + (num % 26)) + letters;
num = Math.floor(num / 26);
}
return letters;
}
You need to make sure that you use the correct value when taking the mod.
function intToAlpha( int ) {
var asciiStart = 97,
alphaMax = 26,
asciiCode,
char,
alpha = "";
while(int > 0) {
char = String.fromCharCode(asciiStart + ((int-1) % alphaMax));
alpha = char + alpha;
int = Math.floor((int-1)/26);
}
return alpha;
}
A while back I needed the same thing in SQL, so I asked (and answered) the question Multi-base conversion - using all combinations for URL shortener.
The thing that is making it complicated is that it's not a straight base conversion, as there is no character representing the zero digit.
I converted the SQL function into Javascript:
function tinyEncode(id) {
var code, value, adder;
var chars = 'abcdefghijklmnopqrstuvwxyz';
if (id <= chars.length) {
code = chars.substr(id - 1, 1);
} else {
id--;
value = chars.length;
adder = 0;
while (id >= value * (chars.length + 1) + adder) {
adder += value;
value *= chars.length;
}
code = chars.substr(Math.floor((id - adder) / value) - 1, 1);
id = (id - adder) % value;
while (value > 1) {
value = Math.floor(value / chars.length);
code += chars.substr(Math.floor(id / value), 1);
id = id % value;
}
}
return code;
}
Demo: http://jsfiddle.net/Guffa/mstBe/

Categories