my function does not fill any cases in my array in javascript - javascript

I'm trying here to solve a 4x4 skyscraper (link skyscraper game)
But the problem is that my function place insert 0 number in tab and I don't understand why console log Also I want to specify that I get 0 error: see below why console log: view code
Now when I place myself a number for example here 9, it works, in the sense that it fills the cells of the table. But here I get an error on the declaration of x, why?
console log console log: view code
I won't post all the code for the sake of readability so here is the main function place:
function place(tab, consign, pos)
{
var x = Math.floor(pos / 4);
var y = pos % 4;
if (tab[x][y] != 0)
place(tab, consign, pos + 1);
else
{
for (var index = 1; index <= 4; index++)
{
tab[x][y] = 9;
console.log(tab);
if (colnline_has_no_double(tab, x, y, index))
{
if (x % 4 == 3)
{
if (check_top_range(tab, consign, x) && check_bottom_range(tab, consign, x))
place(tab, consign, pos + 1);
}
if (y % 4 == 3)
{
if (check_left_range(tab, consign, x) && check_right_range(tab, consign, x))
place(tab, consign, pos + 1);
}
place(tab, consign, pos + 1);
}
else
tab[x][y] = 0;
if (is_filled_good(tab, consign))
{
set_data_in_input(tab);
return ;
}
index++;
}
tab[x][y] = 0;
}
}

Related

My code gets into an infinite loop when setting up in p5

I am having real trouble finding where my loop is. I run the code and it hangs. I am trying to make a circuits game where there are circuits for the user to connect. But I am getting stuck at square one even setting up the map to make sure it is solvable. There is an infinite loop but I can't find it I looked and looked... here is the code.`
//maxLength of circut board is the board
let theHighestMaxLength = 10;
let board;
let gridX = 10;
let gridY = 10;
let perX = 10;
let perY = 10;
//s is here so we don't have to pass it everywhere the square we are looking at in functions
let s=null;
//length and usedsquares and begin are here to be used across functions
let length=0;
let usedSquares=0;
let begin=null;
class Asquare {
constructor(x, y) {
this.x = x;
this.y = y;
this.isCircut = false;
this.isWire = false;
this.OtherCircut = null;
this.Left = null;
this.Right = null;
this.Top = null;
this.Bottom = null;
this.otherCircut = null;
this.isBlank = false;
}
drawSelf() {
if (this.isCircut) {
rectMode(CENTER)
var xx = max(this.otherCircut.x, this.x);
var yy = max(this.otherCircut.y, this.y);
fill(color(xx * 25, yy * 25, 0));
square((this.x + 0.5) * perX, (this.y + 0.5) * perY, perX);
}
else if (this.isWire) {
fill(color(this.otherCircut.x * 20, this.otherCircut.y * 20, 0));
circle((this.x + 0.5) * perX, (this.y + 0.5) * perY, perX);
}
else if (this.isBlank) {
fill(color(0, 204, 0))
circle((this.x + 0.5) * perX, (this.y + 0.5) * perY, perX);
}
}
}
function handleWireMove(){
s.isWire = true;
//remember the starting circut
s.otherCircut = begin;
informAll(s);
//the length is used
length++;
}
function informAll() {
//tell all the other squares of s
if (s.x - 1 >= 0) {
board[s.x - 1][s.y].Right = s;
}
if (s.x + 1 < gridX) {
board[s.x + 1][s.y].Left = s;
}
if (s.y - 1 >= 0) {
board[s.x][s.y - 1].Bottom = s;
}
if (s.y + 1 < gridY) {
board[s.x][s.y + 1].Top = s;
}
//the used squares is now higher
usedSquares++;
}
function setup() {
createCanvas(gridX * perX, gridY * perY);
noLoop();
//fill the board with squares
board = new Array(gridX).fill(0).map(() => new Array(gridY).fill(0));
for (var x = 0; x < gridX; x++) {
for (var y = 0; y < gridY; y++) {
board[x][y] = new Asquare(x, y);
}
}
//the number of squares in the grid used
usedSquares = 0;
//till the board is full
while (usedSquares < gridX * gridY) {
//get a random x y
var rx = floor(random(gridX));
var ry = floor(random(gridY));
//create an s and begin var s for every nw square and begin for the first
s = board[rx][ry];
//if this square is already in use
if(s.isBlank||s.isCircut||s.isWire){continue;}
//begin at this square
begin = s;
//if there is some way to go
if (s.Left == null || s.Right == null || s.Top == null || s.Bottom == null) {
// get a random length to try and reach
var maxLength = floor(random(1, theHighestMaxLength))
//the starting length
length = 0;
begin.isCircut = true;
//inform all the surounding squares and increase the number of used
informAll();
//while the length isn't full
while (length <= maxLength) {
//add an option count for left right top botoom
var numOption = s.Left == null ? 1 : 0;
numOption = s.Right == null ? numOption + 1 : numOption;
numOption = s.Top == null ? numOption + 1 : numOption;
numOption = s.Bottom == null ? numOption + 1 : numOption;
//if there are no toptions to move we must stop here ot if the maxLength is reached
if (numOption == 0 || length == maxLength) {
//this is a circut the beigin circut is begin the begin other circut is this final one
s.isCircut = true;
s.isWire = false;
s.otherCicut = begin;
begin.otherCircut = s;
//make sure the loop ends
length=9999;
break;
}
//pick a random direction number
var randomChoiseNumber = floor(random(numOption));
//if left is an option
if (s.Left == null) {
//if r is already 0 that means we picked left
if (randomChoiseNumber == 0) {
//while left is an option and the maxlength isn't hit and left isn't off the map
while (s.Left == null && length < maxLength && s.x - 1 >= 0) {
//set s to the left
s = board[s.x - 1][s.y];
//handleWireMove the move
handleWireMove()
}
//continue we used the direction
continue;
} else {
//we passed an option reduce the number
randomChoiseNumber--;
}
}
//if right is an option
if (s.Right == null) {
//if this is the zero choice
if (randomChoiseNumber == 0) {
//if right is not off the map and an option while the length is not hit
while (s.Right == null && length < maxLength && s.x + 1 < gridX) {
//set s to right
s = board[s.x + 1][s.y];
//handleWireMove the move
handleWireMove();
}
continue;
} else {
//reduce the number as we passed an option
randomChoiseNumber--;
}
}
//if top is an option
if (s.Top == null) {
//if this is the zero option
if (randomChoiseNumber == 0) {
//while top is a choise and the length is not reached and top is not off the map
while (s.Top == null && length < maxLength && s.y - 1 >= 0) {
//move to the top
s = board[s.x][s.y - 1];
//handleWireMove the move
handleWireMove();
}
//continue the direction is used up
continue;
} else {
//we passed a number reduce the choise number
randomChoiseNumber--;
}
}
//if bottom is an option
if (s.Bottom == null) {
//if this is the zero option
if (randomChoiseNumber == 0) {
//while bottom is a choise and the length is not reached and it is not off the map
while (s.Bottom == null && length < maxLength && s.y + 1 < gridY) {
//go to the bottom
s = board[s.x][s.y + 1];
//handleWireMove the move
handleWireMove();
}
}
}
}
}
else {
//if there was no way to go the square is blank tell the others and increace usedSquares
s.isBlank = true;
informAll();
}
}
}
function drawAll() {
board.forEach(a => a.forEach(b => b.drawSelf()));
}
function draw() {
background(gridX * perX);
drawAll();
}
The problem is in the setup function but I can't find it. Please help
The problem is that you compute the number of available numOption only based on whether the randomly selected grid square has unconnected slots (Left, Top, Right, or Bottom) without considering whether the value of s.x and s.y make it possible to select one of those directions). As a result your inner loop repeats infinitely because it never calls handleWireMove() and thus never increases length.
The code below is modified with some extra logging and it becomes obvious where the infinite loop is (it eventually prints 'we failed to make a choice. that is not good' forever):
//the number of squares in the grid used
usedSquares = 0;
//till the board is full
while (usedSquares < gridX * gridY) {
console.log(usedSquares);
//get a random x y
var rx = floor(random(gridX));
var ry = floor(random(gridY));
//create an s and begin var s for every nw square and begin for the first
s = board[rx][ry];
//if this square is already in use
if (s.isBlank || s.isCircut || s.isWire) {
continue;
}
//begin at this square
begin = s;
//if there is some way to go
if (
s.Left == null ||
s.Right == null ||
s.Top == null ||
s.Bottom == null
) {
// get a random length to try and reach
var maxLength = floor(random(1, theHighestMaxLength));
//the starting length
length = 0;
begin.isCircut = true;
//inform all the surounding squares and increase the number of used
informAll();
//while the length isn't full
console.log('beggining inner loop');
while (length <= maxLength) {
//add an option count for left right top botoom
var numOption = s.Left == null ? 1 : 0;
numOption = s.Right == null ? numOption + 1 : numOption;
numOption = s.Top == null ? numOption + 1 : numOption;
numOption = s.Bottom == null ? numOption + 1 : numOption;
//if there are no toptions to move we must stop here ot if the maxLength is reached
if (numOption == 0 || length == maxLength) {
//this is a circut the beigin circut is begin the begin other circut is this final one
s.isCircut = true;
s.isWire = false;
s.otherCicut = begin;
begin.otherCircut = s;
//make sure the loop ends
console.log('no options, abort');
length = 9999;
break;
}
//pick a random direction number
var randomChoiseNumber = floor(random(numOption));
//if left is an option
if (s.Left == null) {
//if r is already 0 that means we picked left
if (randomChoiseNumber == 0) {
//while left is an option and the maxlength isn't hit and left isn't off the map
while (s.Left == null && length < maxLength && s.x - 1 >= 0) {
//set s to the left
s = board[s.x - 1][s.y];
//handleWireMove the move
handleWireMove();
}
//continue we used the direction
continue;
} else {
//we passed an option reduce the number
randomChoiseNumber--;
}
}
//if right is an option
if (s.Right == null) {
//if this is the zero choice
if (randomChoiseNumber == 0) {
//if right is not off the map and an option while the length is not hit
while (s.Right == null && length < maxLength && s.x + 1 < gridX) {
//set s to right
s = board[s.x + 1][s.y];
//handleWireMove the move
handleWireMove();
}
continue;
} else {
//reduce the number as we passed an option
randomChoiseNumber--;
}
}
//if top is an option
if (s.Top == null) {
//if this is the zero option
if (randomChoiseNumber == 0) {
//while top is a choise and the length is not reached and top is not off the map
while (s.Top == null && length < maxLength && s.y - 1 >= 0) {
//move to the top
s = board[s.x][s.y - 1];
//handleWireMove the move
handleWireMove();
}
//continue the direction is used up
continue;
} else {
//we passed a number reduce the choise number
randomChoiseNumber--;
}
}
//if bottom is an option
if (s.Bottom == null) {
//if this is the zero option
if (randomChoiseNumber == 0) {
//while bottom is a choise and the length is not reached and it is not off the map
while (s.Bottom == null && length < maxLength && s.y + 1 < gridY) {
//go to the bottom
s = board[s.x][s.y + 1];
//handleWireMove the move
handleWireMove();
}
}
}
console.log('we failed to make a choice. that is not good');
}
console.log('exited inner loop');
} else {
//if there was no way to go the square is blank tell the others and increace usedSquares
s.isBlank = true;
informAll();
}
}
Here is a working version that correctly checks not only if there is an opening to the Left/Top/Right/Bottom, but also if that direction is possible (not off the edge of the grid):
//maxLength of circut board is the board
let theHighestMaxLength = 10;
let board;
let gridX = 10;
let gridY = 10;
let perX = 10;
let perY = 10;
//s is here so we don't have to pass it everywhere the square we are looking at in functions
let s = null;
//length and usedsquares and begin are here to be used across functions
let length = 0;
let usedSquares = 0;
let begin = null;
class Asquare {
constructor(x, y) {
this.x = x;
this.y = y;
this.isCircut = false;
this.isWire = false;
this.OtherCircut = null;
this.Left = null;
this.Right = null;
this.Top = null;
this.Bottom = null;
this.otherCircut = null;
this.isBlank = false;
}
drawSelf() {
if (this.isCircut) {
rectMode(CENTER);
var xx = max(this.otherCircut.x, this.x);
var yy = max(this.otherCircut.y, this.y);
fill(color(xx * 25, yy * 25, 0));
square((this.x + 0.5) * perX, (this.y + 0.5) * perY, perX);
} else if (this.isWire) {
fill(color(this.otherCircut.x * 20, this.otherCircut.y * 20, 0));
circle((this.x + 0.5) * perX, (this.y + 0.5) * perY, perX);
} else if (this.isBlank) {
fill(color(0, 204, 0));
circle((this.x + 0.5) * perX, (this.y + 0.5) * perY, perX);
}
}
}
function handleWireMove() {
s.isWire = true;
//remember the starting circut
s.otherCircut = begin;
informAll(s);
//the length is used
length++;
}
function informAll() {
//tell all the other squares of s
if (s.x - 1 >= 0) {
board[s.x - 1][s.y].Right = s;
}
if (s.x + 1 < gridX) {
board[s.x + 1][s.y].Left = s;
}
if (s.y - 1 >= 0) {
board[s.x][s.y - 1].Bottom = s;
}
if (s.y + 1 < gridY) {
board[s.x][s.y + 1].Top = s;
}
//the used squares is now higher
usedSquares++;
}
function setup() {
createCanvas(gridX * perX, gridY * perY);
noLoop();
//fill the board with squares
board = new Array(gridX).fill(0).map(() => new Array(gridY).fill(0));
for (var x = 0; x < gridX; x++) {
for (var y = 0; y < gridY; y++) {
board[x][y] = new Asquare(x, y);
}
}
//the number of squares in the grid used
usedSquares = 0;
//till the board is full
while (usedSquares < gridX * gridY) {
console.log(usedSquares);
//get a random x y
var rx = floor(random(gridX));
var ry = floor(random(gridY));
//create an s and begin var s for every nw square and begin for the first
s = board[rx][ry];
//if this square is already in use
if (s.isBlank || s.isCircut || s.isWire) {
continue;
}
//begin at this square
begin = s;
//if there is some way to go
if (
s.Left == null ||
s.Right == null ||
s.Top == null ||
s.Bottom == null
) {
// get a random length to try and reach
var maxLength = floor(random(1, theHighestMaxLength));
//the starting length
length = 0;
begin.isCircut = true;
//inform all the surounding squares and increase the number of used
informAll();
//while the length isn't full
console.log('beggining inner loop');
while (length <= maxLength) {
//add an option count for left right top botoom
var numOption = s.Left == null && s.x - 1 >= 0 ? 1 : 0;
numOption = s.Right == null && s.x + 1 < gridX ? numOption + 1 : numOption;
numOption = s.Top == null && s.y - 1 >= 0 ? numOption + 1 : numOption;
numOption = s.Bottom == null && s.y + 1 < gridY ? numOption + 1 : numOption;
//if there are no toptions to move we must stop here ot if the maxLength is reached
if (numOption == 0 || length == maxLength) {
//this is a circut the beigin circut is begin the begin other circut is this final one
s.isCircut = true;
s.isWire = false;
s.otherCicut = begin;
begin.otherCircut = s;
//make sure the loop ends
console.log('no options, abort');
length = 9999;
break;
}
//pick a random direction number
var randomChoiseNumber = floor(random(numOption));
//if left is an option
if (s.Left == null && s.x - 1 >= 0) {
//if r is already 0 that means we picked left
if (randomChoiseNumber == 0) {
//while left is an option and the maxlength isn't hit and left isn't off the map
while (s.Left == null && length < maxLength && s.x - 1 >= 0) {
//set s to the left
s = board[s.x - 1][s.y];
//handleWireMove the move
handleWireMove();
}
//continue we used the direction
continue;
} else {
//we passed an option reduce the number
randomChoiseNumber--;
}
}
//if right is an option
if (s.Right == null && s.x + 1 < gridX) {
//if this is the zero choice
if (randomChoiseNumber == 0) {
//if right is not off the map and an option while the length is not hit
while (s.Right == null && length < maxLength && s.x + 1 < gridX) {
//set s to right
s = board[s.x + 1][s.y];
//handleWireMove the move
handleWireMove();
}
continue;
} else {
//reduce the number as we passed an option
randomChoiseNumber--;
}
}
//if top is an option
if (s.Top == null && s.y - 1 >= 0) {
//if this is the zero option
if (randomChoiseNumber == 0) {
//while top is a choise and the length is not reached and top is not off the map
while (s.Top == null && length < maxLength && s.y - 1 >= 0) {
//move to the top
s = board[s.x][s.y - 1];
//handleWireMove the move
handleWireMove();
}
//continue the direction is used up
continue;
} else {
//we passed a number reduce the choise number
randomChoiseNumber--;
}
}
//if bottom is an option
if (s.Bottom == null && s.y + 1 < gridY) {
//if this is the zero option
if (randomChoiseNumber == 0) {
//while bottom is a choise and the length is not reached and it is not off the map
while (s.Bottom == null && length < maxLength && s.y + 1 < gridY) {
//go to the bottom
s = board[s.x][s.y + 1];
//handleWireMove the move
handleWireMove();
}
}
}
console.log('we failed to make a choice. that is not good');
}
console.log('exited inner loop');
} else {
//if there was no way to go the square is blank tell the others and increace usedSquares
s.isBlank = true;
informAll();
}
}
}
function drawAll() {
board.forEach((a) => a.forEach((b) => b.drawSelf()));
}
function draw() {
background(gridX * perX);
drawAll();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
One closing though: this bug is a symptom of this code being horribly complicated and violating fundamental principles of good design such as not having shared global state that is updated as a side effect of function calls.

Javascript - Better Alternate to Recursive call

I have this following problem.
I need to calculate a number x based on time t, x would be represented as M(t).
We have the following
M(0) = 1
M(1) = 1
M(2) = 2
M(2t) = M(t) + M(t + 1) + t (for t > 1)
M(2t + 1) = M(t - 1) + M(t) + 1 (for t >= 1)
With that being said the first thing i had in mind to implement this is by using recursion
function CalculateForTime(t) {
if (t == 0 || t == 1) {
return 1;
}
else if (t == 2) {
return 2;
}
else if (t % 2 == 0) {
t = t / 2;
return CalculateForTime(t) + CalculateForTime(t + 1) + t;
}
else {
t = (t - 1) / 2;
return CalculateForTime(t - 1) + CalculateForTime(t) + 1;
}
}
This works however it breaks when running on a large number t for example 1^20
I tried looking into tail call recursion or substituting the recursion approach to an iteration approach but couldn't really figure it out.
If tail recursion or iteration is the way to go then please I need help on converting this. If not then I'm open to different methods on making this more optimized.
Thank you,
Omar.
You could use a hash table, because for an array it would generate holes with no value.
function calculateForTime(t) {
var k = t;
if (k in lookup) {
return lookup[k];
}
if (t == 0 || t == 1) {
return lookup[k] = 1;
}
if (t == 2) {
return lookup[k] = 2;
}
if (t % 2 == 0) {
t = t / 2;
return lookup[k] = calculateForTime(t) + calculateForTime(t + 1) + t;
}
t = (t - 1) / 2;
return lookup[k] = calculateForTime(t - 1) + calculateForTime(t) + 1;
}
var lookup = {};
console.log(calculateForTime(1e10));
.as-console-wrapper { max-height: 100% !important; top: 0; }
You could store the values in an array,then theres no need to recalc...
var times=[1,1,2];
function CalculateForTime(t) {
t = Math.floor(t / 2);
return times[t]||(times[t]=CalculateForTime(t) + CalculateForTime(t + 1) + t);
}
console.log(
CalculateForTime(100),
CalculateForTime(1000),
CalculateForTime(10000),
);
console.log(times.slice(0,100));
You could use memoization to avoid recalculating the same values again and again. See https://addyosmani.com/blog/faster-javascript-memoization/
This is the same as what others suggested, except that it separates the algorithm from the caching of values.
function memoize(func){
var cache = {};
return function( arg ){
if(arg in cache) {
return cache[arg];
} else {
return cache[arg] = func( arg );
}
}
}
// Overwrite with a function that remember previous results
CalculateForTime = memoize(CalculateForTime);
Pardon any typos, answered from phone

Damerau-Levenshtein distance Implementation

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'));

Page switching algorithm from Nano editor

I'm writing Nano like editor in javascript and have problem with calculating cursor position and file offset for display:
I have two variables _rows and settings.verticalMoveOffset (which is default set to 9 like in Nano) when you move cursor in line 20 and there are 19 rows the cursor is offset by verticalMoveOffset.
I work on this few hours and can't figure out how to map file pointer (line) to editor cursor and offset of the file (the line from which it start the view).
Last try was
if (y >= this._rows) {
var page = Math.floor(y / (this._rows-this._settings.verticalMoveOffset))-1;
var new_offset = (this._rows - this._settings.verticalMoveOffset) * page;
if (this._offset !== new_offset) {
this._view(new_offset);
}
cursor_y = y - (this._rows*page) + this._settings.verticalMoveOffset;
} else {
if (y <= this._rows - this._settings.verticalMoveOffset - 1 &&
this._offset !== 0) {
this._pointer.x = x;
this._pointer.y = y;
this._view(0);
cursor_y = y;
} else if (this._offset !== 0) {
cursor_y = (y - this._settings.verticalMoveOffset) + 1;
} else {
cursor_y = y;
}
}
It work when I swich from page 1st to page 2nd and back, to 3rd page it switch for 1 line to fast, the cursor_y is -1. I try to put plus or minus 1 in various places when calculating page but it didn't work.
Anybody can help me with this?
I found solution (this._offset is set by this._view)
var offset = this._offset + this._rows - cursor_offset;
if (y-this._offset >= this._rows) {
cursor_y = y - offset;
if (this._offset !== offset) {
this._pointer.x = x;
this._pointer.y = y;
this._view(offset);
}
} else if (y-this._offset < 0) {
var new_offset = this._offset - this._rows + cursor_offset;
this._pointer.x = x;
this._pointer.y = y;
this._view(new_offset);
cursor_y = y - new_offset;
} else {
cursor_y = y - offset + cursor_offset + 1;
}

any more optimisation I can do for this function?

I have a simple box blur function in a graphics library (for JavaScript/canvas, using ImageData) I'm writing.
I've done a few optimisations to avoid piles of redundant code such as looping through [0..3] for the channels instead of copying the code, and having each surrounding pixel implemented with a single, uncopied line of code, averaging values at the end.
Those were optimisations to cut down on redundant lines of code. Are there any further optimisations I can do of that kind, or, better still, any things I can change that may improve performance of the function itself?
Running this function on a 200x150 image area, with a Core 2 Duo, takes about 450ms on Firefox 3.6, 45ms on Firefox 4 and about 55ms on Chromium 10.
Various notes
expressive.data.get returns an ImageData object
expressive.data.put writes the contents of an ImageData back to a canvas
an ImageData is an object with:
unsigned long width
unsigned long height
Array data, a single-dimensional data in the format r, g, b, a, r, g, b, a ...
The code
expressive.boxBlur = function(canvas, x, y, w, h) {
// averaging r, g, b, a for now
var data = expressive.data.get(canvas, x, y, w, h);
for (var i = 0; i < w; i++)
for (var j = 0; j < h; j++)
for (var k = 0; k < 4; k++) {
var total = 0, values = 0, temp = 0;
if (!(i == 0 && j == 0)) {
temp = data.data[4 * w * (j - 1) + 4 * (i - 1) + k];
if (temp !== undefined) values++, total += temp;
}
if (!(i == w - 1 && j == 0)) {
temp = data.data[4 * w * (j - 1) + 4 * (i + 1) + k];
if (temp !== undefined) values++, total += temp;
}
if (!(i == 0 && j == h - 1)) {
temp = data.data[4 * w * (j + 1) + 4 * (i - 1) + k];
if (temp !== undefined) values++, total += temp;
}
if (!(i == w - 1 && j == h - 1)) {
temp = data.data[4 * w * (j + 1) + 4 * (i + 1) + k];
if (temp !== undefined) values++, total += temp;
}
if (!(j == 0)) {
temp = data.data[4 * w * (j - 1) + 4 * (i + 0) + k];
if (temp !== undefined) values++, total += temp;
}
if (!(j == h - 1)) {
temp = data.data[4 * w * (j + 1) + 4 * (i + 0) + k];
if (temp !== undefined) values++, total += temp;
}
if (!(i == 0)) {
temp = data.data[4 * w * (j + 0) + 4 * (i - 1) + k];
if (temp !== undefined) values++, total += temp;
}
if (!(i == w - 1)) {
temp = data.data[4 * w * (j + 0) + 4 * (i + 1) + k];
if (temp !== undefined) values++, total += temp;
}
values++, total += data.data[4 * w * j + 4 * i + k];
total /= values;
data.data[4 * w * j + 4 * i + k] = total;
}
expressive.data.put(canvas, data, x, y);
};
Maybe (just maybe) moving the if checks out as far as possible would be an advantage. Let me present some pseudo-code:
I'll just call the code looping over k "inner loop" for simplicity
// do a specialized version of "inner loop" that assumes i==0
for (var i = 1; i < (w-1); i++)
// do a specialized version of "inner loop" that assumes j==0 && i != 0 && i != (w-1)
for (var j = 1; j < (h-1); j++)
// do a general version of "inner loop" that can assume i != 0 && j != 0 && i != (w-1) && j != (h-1)
}
// do a specialized version of "inner loop" that assumes j == (h - 1) && i != 0 && i != (w-1)
}
// do a specialized version of "inner loop" that assumes i == (w - 1)
This would drastically reduce the number if if checks, since the majority of operations would need none of them.
If the only way you use var data is as data.data then you can change:
var data = expressive.data.get(canvas, x, y, w, h);
to:
var data = expressive.data.get(canvas, x, y, w, h).data;
and change every line like:
temp = data.data[4 * w * (j - 1) + 4 * (i - 1) + k];
to:
temp = data[4 * w * (j - 1) + 4 * (i - 1) + k];
and you will save some name lookups.
There may be better ways to optimize it but this is just what I've noticed first.
Update:
Also, if (i != 0 || j != 0) can be faster than if (!(i == 0 && j == 0)) not only because of the negation but also because it can short cuircuit.
(Make your own experiments with == vs. === and != vs. !== because my quick tests showed the results that seem counter-intuitive to me.)
And also some of the tests are done many times and some of the ifs are mutually exclusive but tested anyway without an else. You can try to refactor it having more nested ifs and more else ifs.
A minor optimization:
var imgData = expressive.data.get(canvas, x, y, w, h);
var data = imgData.data;
// in your if statements
temp = data[4 * w * (j - 1) + 4 * (i - 1) + k];
expressive.data.put(canvas, imgData, x, y)
You could also perform some minor optimizations in your indices, for example:
4 * w * (j - 1) + 4 * (i - 1) + k // is equal to
4 * ((w * (j-1) + (i-1)) + k
var jmin1 = (w * (j-1))
var imin1 = (i-1)
//etc, and then use those indices at the right place
Also, put {} after every for-statement you have in your code. The 2 additional characters won't make a big difference. The potential bugs will.
You could pull out some common expressions:
for (var i = 0; i < w; i++) {
for (var j = 0; j < h; j++) {
var t = 4*w*j+4*i;
var dt = 4*j;
for (var k = 0; k < 4; k++) {
var total = 0, values = 0, temp = 0;
if (!(i == 0 && j == 0)) {
temp = data.data[t-dt-4+k];
if (temp !== undefined) values++, total += temp;
}
if (!(i == w - 1 && j == 0)) {
temp = data.data[t-dt+4+k];
if (temp !== undefined) values++, total += temp;
}
if (!(i == 0 && j == h - 1)) {
temp = data.data[t+dt-4+k];
if (temp !== undefined) values++, total += temp;
}
if (!(i == w - 1 && j == h - 1)) {
temp = data.data[t+dt+4+k];
if (temp !== undefined) values++, total += temp;
}
if (!(j == 0)) {
temp = data.data[t-dt+k];
if (temp !== undefined) values++, total += temp;
}
if (!(j == h - 1)) {
temp = data.data[t+dt+k];
if (temp !== undefined) values++, total += temp;
}
if (!(i == 0)) {
temp = data.data[t-4+k];
if (temp !== undefined) values++, total += temp;
}
if (!(i == w - 1)) {
temp = data.data[t+4+k];
if (temp !== undefined) values++, total += temp;
}
values++, total += data.data[t+k];
total /= values;
data.data[t+k] = total;
}
}
}
You could try moving the loop over k so it's outermost, and then fold the +k into the definition of t, saving a bit more repeated calculation. (That might turn out to be bad for memory-locality reasons.)
You could try moving the loop over j to be outside the loop over i, which will give you better memory locality. This will matter more for large images; it may not matter at all for the size you're using.
Rather painful but possibly very effective: you could lose lots of conditional operations by splitting your loops up into {0,1..w-2,w-1} and {0,1..h-2,h-1}.
You could get rid of all those undefined tests. Do you really need them, given that you're doing all those range checks?
Another way to avoid the range checks: you could pad your image (with zeros) by one pixel along each edge. Note that the obvious way to do this will give different results from your existing code at the edges; this may be a good or a bad thing. If it's a bad thing, you can work out the appropriate value to divide by.
the declaration of temp = 0 is not necessary, just write var total = 0, values = 0, temp;.
The next thing is to loop backwards.
var length = 100,
i;
for (i = 0; i < length; i++) {}
is slower than
var length = 100;
for (; length != 0; length--) {}
The third tip is to use Duffy's Device for huge for loops.

Categories