javascript grid help - javascript

I am working on looping through a few grid items and was hoping someone could help me find a way to figure out how to get to the items that I need and possibly I will better understand how to access the items in the grid. So here is the grid.
[0] [1] [2] [3] [4]
[5] [6] [7] [8] [9]
[10] [11] [12] [13] [14]
[15] [16] [17] [18] [19]
[20] [21] [22] [23] [24]
This is basically a 5x5 grid, however it could be any size but I just used this for the example. There are two ways I would like to loop through this. The first one being in this order:
0,1,2,3,4,9,14,19,24,23,22,21,20,15,10,5,6,7,8,13,18,17,16,11,12
Basically all that is doing is going around the outside starting from the top left. The next way I would want to loop through that is through the same exact values except in reverse order (basically inside out instead of outside in) and actually thinking about this now I could just loop through the first method backwards. If anyone can help me with this it would be great. I really want to learn more on how to loop through items in crazy arrangements like this.

This function
function n(i, w, h)
{
if (i < w)
return i;
if (i < w + h-1)
return w-1 + (i-w+1)*w;
if (i < w + h-1 + w-1)
return w-1 + (h-1)*w - (i - (w + h-1 - 1));
if (i < w + h-1 + w-1 + h-2)
return (h - (i - (w + w-1 + h-1 - 2)))*w;
var r = n(i - (w-1)*2 - (h-1)*2, w-2, h-2);
var x = r % (w-2);
var y = Math.floor(r / (w-2));
return (y+1)*w + (x+1);
}
accepts as input
i: Index of the item you're looking for
w: Width of the grid
h: Height of the grid
and returns the corresponding element of the grid assuming that clock-wise spiral traversal.
The implementation simply checks if we're on the top side (i<w), on the downward right side (i<w+h-1) and so on and for these cases it computes the cell element explicitly.
If we complete one single trip around the spiral then it calls recursively itself to find the element in the inner (w-2)*(h-2) grid and then extracts and adjusts the two coordinates considering the original grid size.
This is much faster for big grids than just iterating and emulating the spiral walk, for example computing n(123121, 1234, 3012) is immediate while the complete grid has 3712808 elements and a walk of 123121 steps would require quite a long time.

You just need a way to represent the traversal pattern.
Given an NxM matrix (e.g. 5x5), the pattern is
GENERAL 5x5
------- -------
N right 5
M-1 down 4
N-1 left 4
M-2 up 3
N-2 right 3
M-3 down 2
N-3 left 2
M-4 up 1
N-4 right 1
This says move 5 to the right, 4 down, 4 left, 3 up, 3 right, 2 down, 2 left, 1 up, 1 right. The step size shifts after each two iterations.
So, you can track the current "step-size" and the current direction while decrementing N, M until you reach the end.
IMPORTANT: make sure to write down the pattern for a non-square matrix to see if the same pattern applies.

Here's the "walking" method. Less efficient, but it works.
var arr = new Array();
for(var n=0; n<25; n++) arr.push(n);
var coords = new Array();
var x = 0;
var y = 0;
for(var i=0; i<arr.length; i++) {
if( x > 4 ) {
x = 0;
y++;
}
coords[i] = {'x': x, 'y': y};
x++;
}
// okay, coords contain the coordinates of each item in arr
// need to move along the perimeter until a collision, then turn.
// start at 0,0 and move east.
var dir = 0; // 0=east, 1=south, 2=west, 3=north.
var curPos = {'x': 0, 'y': 0};
var resultList = new Array();
for(var x=0; x<arr.length; x++) {
// record the current position in results
var resultIndex = indexOfCoords(curPos, coords);
if(resultIndex > -1) {
resultList[x] = arr[resultIndex];
}
else {
resultList[x] = null;
}
// move the cursor to a valid position
var tempCurPos = movePos(curPos, dir);
var outOfBounds = isOutOfBounds(tempCurPos, coords);
var itemAtTempPos = arr[indexOfCoords(tempCurPos, coords)];
var posInList = resultList.indexOf( itemAtTempPos );
if(outOfBounds || posInList > -1) {
dir++;
if(dir > 3) dir=0;
curPos = movePos(curPos, dir);
}
else {
curPos = tempCurPos;
}
}
/* int indexOfCoords
*
* Searches coordList for a match to myCoords. If none is found, returns -1;
*/
function indexOfCoords(myCoords, coordsList) {
for(var i=0; i<coordsList.length; i++) {
if(myCoords.x == coordsList[i].x && myCoords.y == coordsList[i].y) return i;
}
return -1;
}
/* obj movePos
*
* Alters currentPosition by incrementing it 1 in the direction provided.
* Valid directions are 0=east, 1=south, 2=west, 3=north
* Returns the resulting coords as an object with x, y.
*/
function movePos(currentPosition, direction) {
var newPosition = {'x':currentPosition.x, 'y':currentPosition.y};
if(direction == 0) {
newPosition.x++;
}
else if(direction == 1) {
newPosition.y++;
}
else if(direction == 2) {
newPosition.x--;
}
else if(direction == 3) {
newPosition.y--;
}
return newPosition;
}
/* bool isOutOfBounds
*
* Compares the x and y coords of a given position to the min/max coords in coordList.
* Returns true if the provided position is outside the boundaries of coordsList.
*
* NOTE: This is just one, lazy way of doing this. There are others.
*/
function isOutOfBounds(position, coordsList) {
// get min/max
var minx=0, miny=0, maxx=0, maxy=0;
for(var i=0; i<coordsList.length; i++) {
if(coordsList[i].x > maxx) maxx = coordsList[i].x;
else if(coordsList[i].x < minx) minx = coordsList[i].x;
if(coordsList[i].y > maxy) maxy = coordsList[i].y;
else if(coordsList[i].y < miny) miny = coordsList[i].y;
}
if(position.x < minx || position.x > maxx || position.y < miny || position.y > maxy) return true;
else return false;
}
This will move through the grid in a direction until it hits the bounds or an item already in the results array, then turn clockwise. It's pretty rudimentary, but I think it would do the job. You could reverse it pretty simply.
Here's a working example: http://www.imakewidgets.com/test/walkmatrix.html

Related

How to select concentric elements of a 2d matrix in javascript?

How to select/group together concentric elements of a 2d matrix?
Following is the code for creating a 2d array.
function Create2DArray(rows) {
var arr = [];
for (var i=0;i<rows;i++) {
arr[i] = [];
}
return arr;
}
imagine an element at the centre of the matrix with address i,j.
How can I separate out the cells in the following manner?
such that only the elements highlighted are grouped together / separated out.
It would be great if a mathematical algorithm, rather than looping a lot. (as this will be more efficient)
NOTE
The above approach works only if the matrix is of odd size i.e. 3 x 3 , 5 x 5, 7 x 7 etc... So if there is a way to implement this for even numbered matrices greater than 3, please suggest that too (for matrices greater than or equal to 4 there is a subset of odd sized matrix which we can use of to implement the above algorithm & discard the remaining)..
Please use math/ indices to cut out concentric cells, rather than using traditional iterators, or anything similar, as this allows faster calculations (...as you can start from the central address (or index) and decrement/increment out) (if possible, if not possible you can suggest any approach)
the possible output can be either an array or an object of corresponding cell addresses
like spanning like this:
index/key 1-> level 1 concentric cells
index/key 2-> level 2 concentric cells
index/key 3-> level 3 concentric cells
...
or possible output can be just a way of traversing each successive levels rather than grouping out the concentric cells; Like if concentric cells in level one is traversed the say alert(level 1)... then after level two alert(level 2) and so on...
You could start from the center point (can be any point with row/column) and then you loop each row and column and do the checking based on the space from that center point.
function genMatrix(rows, cols) {
return Array.from(Array(rows), () => {
return Array(cols).fill(0)
})
}
function select(matrix, [cRow, cCol], space = 0) {
for (let i = 0; i < matrix.length; i++) {
for (let j = 0; j < matrix[i].length; j++) {
if (space === 0) {
// for center point
if (i == cRow && j == cCol) {
matrix[i][j] = 3
}
} else {
// for horizontal points
if (i == cRow - space || i == cRow + space) {
if (j <= cCol + space && j >= cCol - space) {
matrix[i][j] = 3
}
}
// for vertical points
if (j == cCol - space || j == cCol + space) {
if (i <= cRow + space && i >= cRow - space) {
matrix[i][j] = 3
}
}
}
}
}
return matrix
}
const center = [3, 3];
const result = [
select(genMatrix(7, 7), center),
select(genMatrix(7, 7), center, 1),
select(genMatrix(7, 7), center, 2),
select(genMatrix(7, 7), center, 3)
]
// for the demo
result.forEach(matrix => {
matrix.forEach(row => console.log(JSON.stringify(row)))
console.log('-'.repeat(20))
})

Trying to solve a zigzag pattern for an algorithm question

The question is as follows:
The string "PAYPALISHIRING" is written in a zigzag pattern on a given number of rows like this: (you may want to display this pattern in a fixed font for better legibility)
P A H N
A P L S I I G
Y I R
And then read line by line: "PAHNAPLSIIGYIR"
Write the code that will take a string and make this conversion given a number of rows:
string convert(string s, int numRows);
Example 1:
Input: s = "PAYPALISHIRING", numRows = 3
Output: "PAHNAPLSIIGYIR"
Example 2:
Input: s = "PAYPALISHIRING", numRows = 4
Output: "PINALSIGYAHRPI"
Explanation:
P I N
A L S I G
Y A H R
P I
I have written the following code, but I am stuck in terms of how to flag the row as one time to be downward moving, where I increment the start row, but when it's zigzagging back to the top, it should be decremented. I am unable to figure out the logic to make this work without affecting the downward movement. Any help would be appreciated.
const convert = (s, numRows) => {
let startRow = 0
let endRow = numRows - 1
let startColumn = 0
let endColumn = Math.floor((s.length / 2) - 1)
s = s.split('')
let results = []
// to setup the columns
for (let i = 0; i < numRows; i++) {
results.push([])
}
while (startRow <= endRow && startColumn <= endColumn && s.length) {
for (let i = startRow; i <= endRow; i++) {
results[i][startColumn] = s.shift()
}
for (let i = endRow - 1; i >= startRow; i--) {
results[i][startColumn + 1] = s.shift()
startColumn++
}
//this line seems to be the issue
startRow++
}
return results
}
console.log(convert('PAYPALISHIRING', 4))
I rewrote your while loop as follows where I simply walk a "zigzag" pattern! Hopefully, it is simple enough to understand.
let c=0, row=0,col=0, down=0;
while(c<s.length) {
results[row][col]=s[c];
if(down==0) { // moving down
row++;
if(row==numRows) {
down = 1;
col++;
row-=2;
}
} else { // moving up
row--;
col++;
if(row==0) {
down=0;
}
}
c++;
}
Ps. Above code does not handle numRows < 3 so you have to manage them before this loop.
My precalculus is a little rusty, but the logic behind this problem seems like a sine wave. I made a math error somewhere in creating the sin equation that prevents this from working (r never equals c with the current paramaters), but hopefully this will help if this is the direction you choose to go in.
/*If x-axis is position in string, and y-axis is row number...
n=number of rows
Equation for a sin curve: y = A sin(B(x + C)) + D
D=vertical shift (y value of mid point)
D=median of 1 and n
n: median:
1 1
2 1.5
3 2
4 2.5
5 3
6 3.5
7 4
median=(n+1)/2
D=(n+1)/2
A=amplitude (from the mid-point, how high does the curve go)
median + amplitude = number of rows
amplitude = number of rows - median
A=n-D
C=phase shift
Phase shift for a sin curve starting at its lowest point: 3π/2
(so at time 1, row number is 1, and curve goes up from there)
C=3π/2
Period is 2π/B
n p
3 4
4 6
5 8
6 10
period=2(n-1)
2(n-1)=2π/B
B(2(n-1)=2π
B=2π/2(n-1)
B=π/(n-1)
Variables:
s = string
n = number of rows
c = current row number being evaluated
p = position in string
r = row number
*/
var output='';
function convert(s,n) {
D=(n+1)/2
A=n-D
C=(3*Math.PI)/2
B=Math.PI/(n-1)
for (c=1;c<=n;c++) { //loop from 1st row to number of rows
for (p=1;p<=s.length;p++) { //loop from 1st to last character in string
r=A*Math.sin(B*(p+C))+D //calculate the row this character belongs in
if (r==c) { output+= s.charAt(r) } //if the character belongs in this row, add it to the output variable. (minus one because character number 1 is at position 0)
}}
//do something with output here
}

Javascript - Flood-Fill and scanLine algorithms are line-based floods but I want square based floods

I have a flood-fill algorithm (Flood-fill) to fill a 24x24 matrix. I would like to draw a shape as similar to a square using exactly cspots spots for each group as cspots can contain any number. The total of all groups cspots value will equal (24*24) so as the drawing progresses, the areas will become less and less square-like but I would like to keep the semblance of a square. In this example, there are 10 groups of varying cspots values and they need to be all drawn within the 24*24 matrix as square-like as possible. Matrix is 24x24 here but will be bigger with more groups in production. Code:
Main code:
var cspots, // number of spots per group
gArr=[]; // global array which contains all group spots
var tArr = new Array(gArr.length); // touch array for flood-fill
for(var spot in inArr) {
for (var tspot in tArr) // initialise touch array
tArr[tspot]=0;
for(gspot in gArr) { // find lowest open y*24+x ordinal
if (gArr[gspot][0] == 0)
break;
tArr[gspot]=1;
}
cspots = inArr[spot].GD;
userFill(gArr[gspot][1],gArr[gspot][2],inArr[spot].KY,tArr);
}
function userFill(x,y,elem,tArr) {
var gord, qt=0;
if (!cspots) return;
if ((x >= 0) && (x <= 23) && (y >= 0) && (y <= 23)) {
gord = y*24 + x;
if (gArr[gord][0] != 0 || tArr[gord])
return;
gArr[gord][0] = elem;
tArr[gord] = 1;
--cspots;
userFill(x+1,y,elem,tArr);
userFill(x-1,y,elem,tArr);
// before the y-change we need to see if there are any open spots on this line
for(gord=y*24; gord<=(y*24)+23; gord++) {
if (gArr[gord][0] == 0) {
qt=1;
break;
}
}
if (!qt) {
userFill(x,y+1,elem,tArr);
userFill(x,y-1,elem,tArr);
}
}
};
This is a standard flood-fill recursive algorithm (with an accompanying touch array to mark any touches) with the additional code that I check if all x-values are set to non-zero on each x-plane before changing the y-value. This produces a matrix like this:
The problem is that it doesn't look very good (imo) as most of the areas are strung-out along the x-plane. What I want is each different group area to be in the shape of a square as much as I can. Sort-of like this example (using letters to indicate the different group areas):
V V V W W W W X X X X X
V V Y W W W W X X X X Z
Y Y Y W W W W Z Z Z Z Z
Y Y W W W W Z Z Z Z Z
... and so on
So I have changed the userFill to look at a boxX variable which is just the (sqrt of each area)+1, which hopefully I can use to limit each area to make a square-shape. And a preX variable to store the anchor point from each group area so I know how many spots have been added. Here's the new userFill:
Main code:
var tArr = new Array(gArr.length);
for(var spot in inArr) {
for (var tspot in tArr) // initialise touch array
tArr[tspot]=0;
for(gspot in gArr) { // find lowest open y*24+x ordinal
if (gArr[gspot][0] == 0)
break;
tArr[gspot]=1;
}
cspots = inArr[spot].GD;
boxX = Math.ceil(Math.sqrt(cspots));
preX = gArr[gspot][1];
userFill(gArr[gspot][1],gArr[gspot][2],inArr[spot].KY,tArr);
}
function userFill(x,y,elem,tArr) {
var gord, qt=0;
if (!cspots) return;
if ((x >= 0) && (x <= 23) && (y >= 0) && (y <= 23)) {
gord = y*24 + x;
if (gArr[gord][0] != 0 || tArr[gord])
return;
gArr[gord][0] = elem;
tArr[gord] = 1;
--cspots;
// before the x-change we need to see if we have done a boxX number of changes to maintain square-shape
if (Math.abs(x-preX) == boxX) {
userFill(preX,y+1,elem,tArr);
userFill(preX,y-1,elem,tArr);
return;
}
userFill(x+1,y,elem,tArr);
userFill(x-1,y,elem,tArr);
// before the y-change we need to see if there are any open spots on this line
for(gord=y*24; gord<=(y*24)+boxX; gord++) {
if (gArr[gord][0] == 0) {
qt=1;
break;
}
}
if (!qt) {
userFill(x,y+1,elem,tArr);
userFill(x,y-1,elem,tArr);
}
}
};
The only difference is that I check if boxX spots have been added and then call userFill recursively to change the y-plane.
Here's the output and it looks better as most areas are square-like but obviously it needs work (missing most of the spots, pale-blue group area is very oddly-shaped and not square-like at all), but I wonder if there is a better algorithm out there that changes a flood-fill from line-based to square based.
FIXED:
I used a Breadth-First Search which created square-like structures for each group area. The code is:
function bfsFill(x,y,elem,tArr) {
var gord, i=0, pt, queue=[], cnt=0;
if (!cspots) return;
if (isOutOfBounds(x,y)) return;
queue.push([x,y]);
while(cspots>0 && queue.length>0) {
pt = queue.shift();
gord = pt[1]*24 + pt[0];
tArr[gord] = 1;
gArr[gord][0] = elem;
--cspots;
var rArr = neighbours(pt);
async.eachSeries(rArr, function(el, cb2) {
if (!isOutOfBounds(el[0],el[1])) {
gord = el[1]*24 + el[0];
if (tArr[gord] == 0 && gArr[gord][0] == 0) {
for(var qi in queue) {
if (queue[qi][0] == el[0] && queue[qi][1]==el[1]) {
cb2();
return;
}
}
queue.push(el);
}
}
cb2();
}, function(err) {
});
}
};

Calculating the area of an irregular polygon using JavaScript

I got this as an assignment, but I'm stuck on implementing the area portion. The assignment is already turned in (finished about 80%). I still want to know how to implement this, though. I am stuck on line 84-86. Here is the prompt.
Prompt: Calculate the area of irregularly shaped polygons using JS
INPUT: a nested array of 3-6 coordinates represented as [x,y] (clockwise order)
OUTPUT: area calculated to 2 significant figures
Pseudocode:
loop the input and check for error cases:
a. array length < 3 or array length > 6
b. array is not empty
c. numbers within -10 and 10 range
loop to each inner array, and multiply x and y in the formula below:
sum_x_to_y = (X0 *Y1) + (X1* Y2)...X(n-1)* Yn
sum_y_to_x = (Y0 * X1) + (Y1-X2)...Y(n-1)* Xn
ex:
(0, -10) (7,-10) (0,-8) (0,-10)
| x | y |
| 0 |-10 |
| 7 |-10 |
| 0 |-8 |
| 0 |-10 |
sum_x_to_y = 0*-10 + 7*-8 + 0*-10 = -56
sum_y_to_x = -10*7 + -10*0 + -8*0 = -70
area = (sum_y_to_x - sum_x_to_y) / (2.00)
ex: area = -70 -(-56) = 57/2 = 7
return area.toPrecision(2) to have one sig fig
function PaddockArea(array_coords) {
var sum_x_to_y = 0;
var sum_y_to_x = 0;
var arr_size = array_coords.length;
if (arr_size === 0) {
//check for empty array
console.log("Invalid input. Coordinates cannot be empty.");
}
if (arr_size < 3 || arr_size > 7) {
//check input outside of 3-6 range
console.log("Input out of range.");
}
for (var i = 0; i < arr_size; i++) {
for (var j = 0; j < array_coords[i].length; j++) {
//test for inner coordinates -10 to 10 range
if (array_coords[i][j] < -10 || array_coords[i][j] > 10) {
console.log("Coordinates outside of -10 to 10 range.");
}
// I NEED TO IMPLEMENT TO calc for AREA here
sum_x_to_y += array_coords[i][j] * array_coords[j][i];
sum_y_to_x += array_coords[j][i] * array_coords[i][j];
var area = (sum_y_to_x - sum_x_to_y) / 2;
console.log(area.toPrecision(2) + "acres");
}
}
}
If you're just using Simpson's rule to calculate area, the following function will do the job. Just make sure the polygon is closed. If not, just repeat the first coordinate pair at the end.
This function uses a single array of values, assuming they are in pairs (even indexes are x, odd are y). It can be converted to using an array of arrays containing coordinate pairs.
The function doesn't do any out of bounds or other tests on the input values.
function areaFromCoords(coordArray) {
var x = coordArray,
a = 0;
// Must have even number of elements
if (x.length % 2) return;
// Process pairs, increment by 2 and stop at length - 2
for (var i=0, iLen=x.length-2; i<iLen; i+=2) {
a += x[i]*x[i+3] - x[i+2]*x[i+1];
}
return Math.abs(a/2);
}
console.log('Area: ' + areaFromCoords([1,1,3,1,3,3,1,3,1,1])); // 4
console.log('Area: ' + areaFromCoords([0,-10, 7,-10, 0,-8, 0,-10,])); // 7
Because you haven't posted actual code, I haven't input any of your examples. The sequence:
[[1,0],[1,1],[0,0],[0,1]]
is not a polygon, it's a Z shaped line, and even if converted to a unit polygon can't resolve to "7 acres" unless the units are not standard (e.g. 1 = 184 feet approximately).

d3.js How to simplify a complex path - using a custom algorithm

I've got a very basic example here. http://jsfiddle.net/jEfsh/57/ that creates a complex path - with lots of points. I've read up on an algorithm that may look over the points and create a simpler set of coordinates. Does anyone have any experience with this - examples on how to loop through the path data and pass it through the algorithm - find the shortest set of points to create a more rudimentary version of the shape?
http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
var points = "M241,59L237,60L233,60L228,61L224,61L218,63L213,63L209,65L204,66L199,67L196,68L193,69L189,70L187,72L184,72L182,74L179,75L177,76L175,78L173,79L170,81L168,83L165,85L163,87L161,89L159,92L157,95L157,97L155,102L153,105L152,110L151,113L151,117L151,123L150,137L148,180L148,185L148,189L148,193L148,197L148,202L148,206L149,212L151,218L152,222L154,229L154,232L155,235L157,239L158,241L160,245L162,247L163,249L165,251L167,254L169,256L172,258L175,260L178,261L183,265L188,268L193,270L206,273L213,275L220,275L225,275L232,276L238,277L243,277L249,277L253,277L259,277L266,277L271,277L277,277L281,277L284,277L288,277L293,277L297,276L302,274L305,272L308,271L311,268L313,267L315,264L318,261L322,257L324,254L326,249L329,244L331,241L332,239L334,234L338,230L339,226L341,222L343,218L345,213L347,211L348,207L349,201L351,196L352,192L353,187L353,183L353,180L353,178L354,176L354,173L354,170L354,168L354,167L354,166L354,164L354,162L354,161L354,159L354,158L354,155L354,152L354,149L352,143L349,137L347,133L343,125L340,119 M241,59L340,119";
d3.select("#g-1").append("path").attr("d", points);
//simplify the path
function DouglasPeucker(){
}
/*
//http://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
function DouglasPeucker(PointList[], epsilon)
// Find the point with the maximum distance
dmax = 0
index = 0
end = length(PointList)
for i = 2 to ( end - 1) {
d = shortestDistanceToSegment(PointList[i], Line(PointList[1], PointList[end]))
if ( d > dmax ) {
index = i
dmax = d
}
}
// If max distance is greater than epsilon, recursively simplify
if ( dmax > epsilon ) {
// Recursive call
recResults1[] = DouglasPeucker(PointList[1...index], epsilon)
recResults2[] = DouglasPeucker(PointList[index...end], epsilon)
// Build the result list
ResultList[] = {recResults1[1...end-1] recResults2[1...end]}
} else {
ResultList[] = {PointList[1], PointList[end]}
}
// Return the result
return ResultList[]
end
*/
It's not clear what your problem is exactly. Do you have problems to turn the SVG data string into a list of points? You can use this:
function path_from_svg(svg) {
var pts = svg.split(/[ML]/);
var path = [];
console.log(pts.length);
for (var i = 1; i < pts.length; i++) {
path.push(pts[i].split(","));
}
return path;
}
It is a very simple approach: It splits the string on all move (M) and line (L) commands and treats them as lines. It then splits all substrings on the comma. The first "substring" is ignored, because it is the empty string before the first M. If there is a way to do this better in d3 I haven't found it.
The reverse operation is easier:
function svg_to_path(path) {
return "M" + path.join("L");
}
This is equivalent to svg.line.interpolate("linear").
You can then implement the Douglas-Peucker algorithm on this path data recursively:
function path_simplify_r(path, first, last, eps) {
if (first >= last - 1) return [path[first]];
var px = path[first][0];
var py = path[first][1];
var dx = path[last][0] - px;
var dy = path[last][1] - py;
var nn = Math.sqrt(dx*dx + dy*dy);
var nx = -dy / nn;
var ny = dx / nn;
var ii = first;
var max = -1;
for (var i = first + 1; i < last; i++) {
var p = path[i];
var qx = p[0] - px;
var qy = p[1] - py;
var d = Math.abs(qx * nx + qy * ny);
if (d > max) {
max = d;
ii = i;
}
}
if (max < eps) return [path[first]];
var p1 = path_simplify_r(path, first, ii, eps);
var p2 = path_simplify_r(path, ii, last, eps);
return p1.concat(p2);
}
function path_simplify(path, eps) {
var p = path_simplify_r(path, 0, path.length - 1, eps);
return p.concat([path[path.length - 1]]);
}
The distance to the line is not calculated in a separate function but directly with the formula for the distance of a point to a 2d line from the normal {nx, ny} on the line vector {dx, dy} between the first and last point. The normal is normalised, nx*nx + ny*ny == 1.
When creating the paths, only the first point is added, the last point path[last] is implied and must be added in path_simplify, which is a front end to the recursive function path_simplify_r. This approach was chosen so that concatenating the left and right subpaths does not create a duplicate point in the middle. (This could equally well and maybe cleaner be done by joining p1 and p2.slice(1).)
Here's everything put together in a fiddle: http://jsfiddle.net/Cbk9J/3/
Lots of good references in the comments to this question -- alas they are comments and not suggested answers which can be truly voted on.
http://bost.ocks.org/mike/simplify/
shows interactive use of this kind of thing which references Douglas-Peucker but also Visvalingam.

Categories