How to clip movements to a bounding box? - javascript

I'm struggling to find the logic to output a series of pen positions based on some input format.
The pen's position data should be converted in a way that maintains a state of 'pen-down' and 'pen-up' (I have that working), and that clips any pen-movement beyond the rectangle area for the input (x = -8192, y = -8192) .. (8191, 8191). I could not make that clipping work.
See below instructions for how that clipping is supposed to happen:
Instructions
Example of expected output
inputData:
F0A0417F40004000417FC067086708804001C0670840004000187818784000804000
inputData:
F0A0417F41004000417FC067086708804001C067082C3C18782C3C804000
//For decode inputData//
let commands =[] , command , allPos = "", lastPos;
function proDecode(hex) {
for (let i = 0, len; i < hex.length; i+=len) {
// Opcodes take 1 byte (i.e. 2 hex characters), and
// numbers take 2 bytes (4 characters)
len = hex[i] >= '8' ? 2:4;
let num = hex.slice(i,i+len)
if (len === 2) { // opcode
command = []
commands.push(command)
}
else{
num = penDecode(num);
console.log(num);
}
command.push(num)
}
console.log(commands);
return commands;
}
//for outPut//
unction proOutput(commands){
let isPenDown = false;
let x = 0, y = 0;
let output = "";
let color = ""
for (let [opcode, ...args] of commands) {
if (opcode === 'F0') {
x = y = 0;
isPenDown = false;
color = "CO" + 0 + 0 + 0 + 255 + ";\n";
output += "CLR;\n";
} else if (opcode === '80') {
isPenDown = args[0] > 0;
output += "PEN " + (isPenDown ? "DOWN" : "UP") + ";\n";
} else if (opcode === 'A0') {
color = "CO " + args.join(" ") + ";\n";
output += color
} else if (opcode === 'C0') {
let allPos = "", lastPos;
for (let i = 0; i < args.length; i+=2) {
x += args[i];
y += args[i+1];
if(x <-8192){
x= -8192
} else if (x>8191){
x= 8191
lastPos = ` (${x}, ${y})`;
}else if (y<-8192){
y= -8192
}else if (y>8191){
y= 8191
output += "y PEN UP" + ";\n";
} else{
}
lastPos = ` (${x}, ${y})`;
if (isPenDown) allPos += lastPos;
}
output += "MV" + (allPos || lastPos) + ";\n";
} // else: ignore unknown commands
}
Expected Result
Create the same output of the example above.

You would need to find the intersection point with the bounding box whenever the coordinates move out of bounds (or come back within bounds). In some cases a line segment could even both enter and exit the box, giving two intersections.
To calculate an intersection point between a line segment (from x1, y1 to x2, y2) and a horizontal line at y, you would use this formula for the x-coordinate:
x = x1 + (y - y1) * (x2 - x1) / (y2 - y1)
Obviously the y coordinate of the intersection point is already given by y.
There would be no intersection when one of the following conditions is true:
y1 < y and y2 < y
y1 > y and y2 > y
y1 = y2
So to determine whether a line segment crosses one of the four sides of the bounding box (-8192, -8192) to (8191, 8191), you would check for 4 intersections. You could then find 0, 1 or 2 of such intersections.
Here is the code, which I adapted from my answer to your previous question (on hex encoding):
function intersectionWithAxis(x1, y1, x2, y2, y) {
if (y1 < y && y2 < y || y1 > y && y2 > y || y1 === y2) return []; // No intersection
return [Math.round(x1 + (y - y1) * (x2 - x1) / (y2 - y1)), y];
}
function decode(hex) {
let commands = [];
for (let i = 0, len, command; i < hex.length; i += len) {
// Opcodes take 1 byte (i.e. 2 hex characters), and
// numbers take 2 bytes (4 characters)
len = hex[i] >= "8" ? 2 : 4;
let num = parseInt(hex.slice(i, i+len), 16);
if (len === 2) { // Opcode
command = []; // start a new command
commands.push(command);
} else { // Number. Encoded in offset-binary, using 2 x 7 bits
num = ((num & 0x7F00) >> 1) + (num & 0x7F) - 0x2000;
}
command.push(num); // Push opcode or argument in current command
}
return commands;
}
function disassemble(hex) {
let isPenDown = false;
let isPenInBox = true;
let x = 0, y = 0;
let output = "";
let commands = decode(hex);
for (let [opcode, ...args] of commands) {
if (opcode === 0xF0) {
x = y = 0;
isPenDown = false;
isPenInBox = true;
output += "CLR;\n";
} else if (opcode === 0x80) {
isPenDown = args[0] > 0;
if (isPenInBox) output += "PEN " + (isPenDown ? "DOWN" : "UP") + ";\n";
} else if (opcode === 0xA0) {
output += "CO " + args.join(" ") + ";\n";
} else if (opcode === 0xC0) {
if (!isPenDown) {
for (let i = 0; i < args.length; i += 2) {
let [dx, dy] = args.slice(i, i + 2);
x += dx;
y += dy;
}
output += `MV (${x}, ${y});\n`;
} else {
let buffer = "";
for (let i = 0; i < args.length; i += 2) {
let [dx, dy] = args.slice(i, i + 2);
let toX = x + dx;
let toY = y + dy;
// Get intersections with top, bottom, left and right side of box:
let intersections = [
intersectionWithAxis(x, y, toX, toY, -8192),
intersectionWithAxis(x, y, toX, toY, 8191),
intersectionWithAxis(y, x, toY, toX, -8192).reverse(),
intersectionWithAxis(y, x, toY, toX, 8191).reverse()
].filter(p =>
// Only keep the intersection points
p.length && p.every(x => x >= -8192 && x <= 8191)
);
if (intersections.length === 0) { // Position remains at same side of the box (inside or outside)
if (isPenInBox) buffer += ` (${x}, ${y})`;
} else if (intersections.length === 1) { // Moving from outside to inside of box, or vice versa
// Flush previous positions to output, move to the intersection point, and toggle the pen
output += `MV${buffer} (${intersections[0].join(", ")});\nPEN ${(isPenInBox ? "UP" : "DOWN")};\n`
isPenInBox = !isPenInBox;
// Start new series with positions
buffer = isPenInBox ? ` (${toX}, ${toY})` : "";
} else { // Moving from outside the box, through the box, and outside again
output += `MV (${intersections[0].join(", ")});\nPEN DOWN;\nMV (${intersections[1].join(", ")});\nPEN UP;\n`;
}
x = toX;
y = toY;
}
// Flush previous positions to output
if (buffer) output += `MV${buffer};\n`;
}
} // else: ignore unknown commands
}
return output;
}
// Two samples as in question:
console.log(disassemble("F0A0417F40004000417FC067086708804001C0670840004000187818784000804000"));
console.log("---");
console.log(disassemble("F0A0417F41004000417FC067086708804001C067082C3C18782C3C804000"));
The code you posted in your question has some issues (which is why I used the original code without your changes to it):
It is bad practice to use global variables like commands, command, allPos and lastPos: you change their values within your functions, which gives those functions side effects;
The color variable is set in the following statement, but this value is never used:
color = "CO" + 0 + 0 + 0 + 255 + ";\n";
Also, that value does not follow the required syntax (spaces are missing between the RGB values). Anyway, it serves no purpose in your function, so you can omit it.
The following string does not include the value of y, but a literal "y":
output += "y PEN UP" + ";\n";
Also, generating the output at that spot in the code is wrong: you would need to flush the already gathered coordinates to the output first. And outputting y or its value at this point is not in line with the output syntax either.

Related

Trigonometric Interpolation returns NaN

I'm a musician, who's new to programming. I use JavaScript inside Max Msp (hence the bang() and post() functions) to create a trigonometric interpolation, interpolating between given equidistant points (for testing, only values of sine from [0, 2π) and returning values from the same points). When I run the code, it returns NaN, except for x = 0, as my tau() function returns only 1 in this special case. Could it be, that it has something to do with summing Math.sin results?
var f = new Array(9);
var TWO_PI = 2*Math.PI;
bang();
function bang() {
for(var i = 0; i < f.length; i++) {
f[i] = Math.sin(i/f.length*TWO_PI);
//post("f[" + i + "]: " + Math.round(f[i]*1000)/1000 + "\n");
}
var points = new Array(f.length);
for(var i = 0; i < points.length; i++) {
var idx = i/points.length*TWO_PI;
points[i] = [i, p(idx)];
//post("p[" + points[i][0] + "]: " + Math.round(points[i][1]*1000)/1000 + "\n");
}
console.log("p(2): " + p(2/points.length*TWO_PI) + "\n");
}
function p(x) {
var result = 0;
for(var k = 0; k < f.length; k++) {
result += f[k]*tau(k, x);
}
return result;
}
function tau(k, x) {
var dividend = sinc(1/2*f.length*(x-k/f.length*TWO_PI));
var divisor = sinc(1/2*(x-k/f.length*TWO_PI));
var result = dividend/divisor;
if(f.length%2 == 0) result *= Math.cos(1/2*(x-k/f.length*TWO_PI));
if(x == 0) return 1;
return result;
}
function sinc(x) {
return Math.sin(x)/x;
}
In your tau function, if x equals k / f.length * TWO_PI (which it will since x is multiples of 1 / points.length * TWO_PI) your sinc function divides by 0, making divisor equal to NaN, which then propagates.
You have to be a bit careful in implementing sinc to avoid dividing by 0. One way is to say that if x is small enough we can replace sin(x) by the first few terms of its taylor series, and all the terms are divisible by x.
I don't know javascript but here is the function in C in case it is of use
#define SINC_EPS (1e-6)
// for small x,
// missing sinc terms start with pow(x,4)/120, and value close to 1
// so the error too small to be seen in a double
double sinc( double x)
{ if ( fabs(x) < SINC_EPS)
{ return 1.0 - x*x/6.0;
}
else
{ return sin(x)/x;
}
}

JavaScript method to determine correct path is confusing

I'm learning algorithms and doing JavaScript exercise questions, and I don't understand how one reaches the correct answer for a particular algorithm.
The question provided in the exercise is:
Have the function CorrectPath(str) read the str parameter being
passed, which will represent the movements made in a 5x5 grid of cells
starting from the top left position. The characters in the input
string will be entirely composed of: r, l, u, d, ?. Each of the
characters stand for the direction to take within the grid, for
example: r = right, l = left, u = up, d = down. Your goal is to
determine what characters the question marks should be in order for a
path to be created to go from the top left of the grid all the way to
the bottom right without touching previously travelled on cells in the
grid.
For example, the input drdr??rrddd? should ouptut drdruurrdddd
I've not found a solution on my own. I'm taking a look at a solution provided, and I'm bothered because:
A. pure functions are not used to manipulate values within the CorrectPath function (note the addX() and addY() methods contained within). I'm not convinced the solution provided is using best practices, especially coming from a functional programming background.
B. I don't understand how the steps taken, specifically in the while block and the succeeding for block, are taken to reach the correct answer and why sometimes the missingLetters array has letters remaining and other times not
The working solution provided is below
function CorrectPath(str) {
let x = 0, //start x coord
y = 0, //start y coord
missingLetters = []
const unknowns = str.match(/\W/g)
function addX() {
while(x !== 4) {
if (x > 4) {
x--;
missingLetters.push('l');
} else {
x++;
missingLetters.push('r');
}
}
}
function addY() {
while (y !== 4) {
if (y > 4) {
y--;
missingLetters.push('u');
} else {
y++;
missingLetters.push('d');
}
}
}
//tallies current number of x and y movements
for (let i=0; i<str.length; i++) {
switch (str[i]) {
case 'd':
y += 1;
break;
case 'u':
y -= 1;
break;
case 'l':
x -= 1;
break;
case 'r':
x += 1;
break;
}
}
if (x > y) { addX(); addY(); }
if (y >= x) { addY(); addX(); }
while (missingLetters.length < unknowns.length) {
var pos = missingLetters.length - 1;
if (missingLetters[pos] === 'r') {x += 1; missingLetters.push('r'); addX()}
if (missingLetters[pos] === 'l') {x -= 1; missingLetters.push('l'); addX()}
if (missingLetters[pos] === 'd') {y += 1; missingLetters.push('d'); addY()}
if (missingLetters[pos] === 'u') {y -= 1; missingLetters.push('u'); addY()}
}
var newStr = str.split('');
for (var j=0; j<str.length; j++) {
if (newStr[j] === '?') {
newStr[j] = missingLetters.shift()
}
}
return newStr.join('');
}
CorrectPath(readline());
Here's a solution I found
const dirMap = {
u: { x: 0, y: -1 },
r: { x: 1, y: 0 },
d: { x: 0, y: 1 },
l: { x: -1, y: 0 }
}
function CorrectPath(pathString) {
const map = Array(5*5)
return trace(pathString, map)
}
function trace(path, [...map], x = 0, y = 0, newPath = "") {
const steps = path.split(""),
nextMove = steps.shift()
if (nextMove === undefined) {
if (5 * y + x === (5*5-1)) return newPath
return "Bad move"
}
if (nextMove === "?") {
const moves = availableMoves(x,y,map)
if (!moves.length) return "Bad move"
for(let i = 0; i<moves.length; i++) {
let move = moves[i],
trySteps = [move,...steps].join("")
res = trace(trySteps,map,x,y,newPath)
if (!res || res === "Bad move") continue
else return res
}
return "Bad move"
} else {
if (!canMove(nextMove, x, y, map)) return "Bad move"
const pos = dirMap[nextMove],
newX = pos.x + x,
newY = pos.y + y
newPath += nextMove
map[5*newY+newX] = nextMove
return trace(steps.join(""),map,newX,newY,newPath)
}
}
function availableMoves(x,y,map) {
const steps = []
Object.keys(dirMap).forEach(z => {
if (canMove(z,x,y,map)) steps.push(z)
})
return steps
}
function canMove(dir, xPath, yPath, map) {
const pos = dirMap[dir],
x = pos.x + xPath,
y = pos.y + yPath
if (x > 4 || x < 0 || y > 4 || y < 0) return false
if (map[5*y+x] !== undefined) return false
return true
}
CorrectPath(readline());

Is there a way to reduce the number of coordinates in a complex enclosed SVG path?

What I'd like to do is take an SVG shape drawn by an enclosed path (in this case, a region of a map) and reduce the number of points to create a simpler shape.
I've tried implementing the Ramer-Douglas-Peucker algorithm to reduce the number of points. For example, here's a fiddle using the simplify.js library:
https://jsfiddle.net/0t3n8762/
After reading about the issue, if I understood it correctly, it seems the algorithm isn't really designed to work on enclosed shapes but open ended paths. I tried splitting each path into two (so there are two lines that together make the entire shape) and running the algorithm on each before recombining them, though the results seem essentially identical:
https://jsfiddle.net/caqwL3t7/
It may be (and indeed is quite likely) that I'm just not grasping how the algorithm is supposed to work and am implementing it incorrectly. Or perhaps that I should be trying a completely different method altogether.
coords = pathToCoords($("#JP-01").attr("d"));
for (var i = 0; i < coords.length; i++) {
newCoords[i] = simplify(coords[i], 2);
}
newPath = coordsToPath(newCoords);
$("#JP-01").attr("d", newPath);
What I would want to produce is a simpler path that still retains the overall shape of the original, drawn with fewer points. The actual result is a distorted shape that shares little in common with the original.
As Paul pointed out in the comments, you haven't considered about l and v command in a path.
I made a snippet to show how to achieve your goal but it won't work in all the cases (I guess) and it still needs to be improved.
Kindly find the snippet and comments below.
$("#simplify-1").on("click", function(){
var coords;
var newCoords = [];
var newPath;
var coordsObject;
// get the coordinates from a given path
function pathToCoords(path) {
// save each path individually (.i.e islands are stored separately)
var data = path.match(/(?<=m).*?(?=z)/igs);
var coordsArray = [];
var objectArray = [];
var objectArrayA = [];
var objectArrayB = [];
var objectContainer = [];
// split each pair of coordinates into their own arrays
for (var i = 0; i < data.length; i++) {
// should remove anything between h or v and l instead?
data[i] = data[i].split(/[LlHhVv]/);
coordsArray[i] = [];
for (var j = 0; j < data[i].length; j++) {
coordsArray[i].push(data[i][j].split(",").map(Number));
}
}
// convert each pair of coordinates into an object of x and y
for (var i = 0; i < coordsArray.length; i++) {
objectArray[i] = [];
for (var j = 0; j < coordsArray[i].length; j++) {
objectArray[i].push({
x: coordsArray[i][j][0],
y: coordsArray[i][j][1]
});
}
// split each array of coordinates in half
var halfway = Math.floor(objectArray[i].length / 2);
objectArrayB[i] = JSON.parse(JSON.stringify(objectArray[i]));;
objectArrayA[i] = objectArrayB[i].splice(0, halfway);
}
objectContainer = [objectArrayA, objectArrayB];
return objectContainer;
}
// convert the coordinates back into a string for the path
function coordsToPath(objectContainer) {
var objectArray = [];
var coordsArray = [];
var data;
// recombine the two objectArrays
for (var i = 0; i < objectContainer[0].length; i++) {
objectArray[i] = objectContainer[0][i].concat(objectContainer[1][i])
}
for (var i = 0; i < objectArray.length; i++) {
coordsArray[i] = [];
// take the X and Y values from the objectArray and strip the unwanted information
for (var j = 0; j < objectArray[i].length; j++) {
if (j == 0) {
// add 'M' in front of the first entry
coordsArray[i].push("M" + Object.values(objectArray[i][j]));
} else if (j == objectArray[i].length - 1) {
// add 'z' to the end of the last entry
coordsArray[i].push("l" + Object.values(objectArray[i][j]) + "z");
} else {
// add 'l' in front of each coordinate pair
coordsArray[i].push("l" + Object.values(objectArray[i][j]));
}
}
coordsArray[i] = coordsArray[i].toString();
}
// put everything back into a single valid SVG path string
data = coordsArray.join("");
return data;
}
// -- simplify.js -- //
/*
(c) 2017, Vladimir Agafonkin
Simplify.js, a high-performance JS polyline simplification library
mourner.github.io/simplify-js
*/
(function() {
'use strict';
// to suit your point format, run search/replace for '.x' and '.y';
// for 3D version, see 3d branch (configurability would draw significant performance overhead)
// square distance between 2 points
function getSqDist(p1, p2) {
var dx = p1.x - p2.x,
dy = p1.y - p2.y;
return dx * dx + dy * dy;
}
// square distance from a point to a segment
function getSqSegDist(p, p1, p2) {
var x = p1.x,
y = p1.y,
dx = p2.x - x,
dy = p2.y - y;
if (dx !== 0 || dy !== 0) {
var t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy);
if (t > 1) {
x = p2.x;
y = p2.y;
} else if (t > 0) {
x += dx * t;
y += dy * t;
}
}
dx = p.x - x;
dy = p.y - y;
return dx * dx + dy * dy;
}
// rest of the code doesn't care about point format
// basic distance-based simplification
function simplifyRadialDist(points, sqTolerance) {
var prevPoint = points[0],
newPoints = [prevPoint],
point;
for (var i = 1, len = points.length; i < len; i++) {
point = points[i];
if (getSqDist(point, prevPoint) > sqTolerance) {
newPoints.push(point);
prevPoint = point;
}
}
if (prevPoint !== point) newPoints.push(point);
return newPoints;
}
function simplifyDPStep(points, first, last, sqTolerance, simplified) {
var maxSqDist = sqTolerance,
index;
for (var i = first + 1; i < last; i++) {
var sqDist = getSqSegDist(points[i], points[first], points[last]);
if (sqDist > maxSqDist) {
index = i;
maxSqDist = sqDist;
}
}
if (maxSqDist > sqTolerance) {
if (index - first > 1) simplifyDPStep(points, first, index, sqTolerance, simplified);
simplified.push(points[index]);
if (last - index > 1) simplifyDPStep(points, index, last, sqTolerance, simplified);
}
}
// simplification using Ramer-Douglas-Peucker algorithm
function simplifyDouglasPeucker(points, sqTolerance) {
var last = points.length - 1;
var simplified = [points[0]];
simplifyDPStep(points, 0, last, sqTolerance, simplified);
simplified.push(points[last]);
return simplified;
}
// both algorithms combined for awesome performance
function simplify(points, tolerance, highestQuality) {
if (points.length <= 2) return points;
var sqTolerance = tolerance !== undefined ? tolerance * tolerance : 1;
points = highestQuality ? points : simplifyRadialDist(points, sqTolerance);
points = simplifyDouglasPeucker(points, sqTolerance);
return points;
}
// export as AMD module / Node module / browser or worker variable
if (typeof define === 'function' && define.amd) define(function() {
return simplify;
});
else if (typeof module !== 'undefined') {
module.exports = simplify;
module.exports.default = simplify;
} else if (typeof self !== 'undefined') self.simplify = simplify;
else window.simplify = simplify;
})();
// -- end simplify.js -- //
coords = pathToCoords($("#OriginalJP-01").attr("d"));
for (var i = 0; i < coords.length; i++) {
newCoords[i] = [];
for (var j = 0; j < coords[i].length; j++) {
newCoords[i][j] = simplify(coords[i][j], 1);
}
}
newPath = coordsToPath(newCoords);
$("#JP-01").attr("d", newPath);
});
$("#simplify-2").on("click", function(){
let d = $("#OriginalJP-01").attr("d");
let coordsArray = [];
let data = d.match(/(?<=m).*?(?=z)/igs);
// split each pair of coordinates into the array as an object {x, y}
for (var i = 0; i < data.length; i++) {
let ca = coordsArray[i] = [];
// split data[i] into each coordinate text
let matches = data[i].match(/((\w?-?\d+(\.\d+)?)(,-?\d+(\.\d+)?)?)/g);
for(let j=0;j<matches.length;j++){
let x, y,
text = matches[j],
// split with comma and convert it to a number
temp = text.split(",").map(v=>+v.replace(/^[^\-\d]/g,""));
switch(text[0]){
default:
case "L": // absolute
x = temp[0];
y = temp[1];
break;
case "l": // relative
x = ca[j-1].x + temp[0];
y = ca[j-1].y + temp[1];
break;
case "V": // absolute
x = ca[j-1].x;
y = temp[0];
break;
case "v": // relative
x = ca[j-1].x;
y = ca[j-1].y + temp[0];
break;
case "H": // absolute
x = temp[0];
y = ca[j-1].y;
break;
case "h": // relative
x = ca[j-1].x + temp[0];
y = ca[j-1].y;
break;
}
x = +x.toFixed(2);
y = +y.toFixed(2);
ca.push({x, y});
}
}
let mArray = [];
// calculate the slopes
for(let i=0;i<coordsArray.length;i++){
mArray[i] = [];
for(let j=0;j<coordsArray[i].length-1;j++){
let {x, y} = coordsArray[i][j], // current point's x and y
{x: nx, y: ny} = coordsArray[i][j+1], // next point's x and y
dy = (ny - y);
if(dy === 0) // to check if the denominator is legal or not
// in your case, it would not enter here
mArray[i].push(Infinity);
else
mArray[i].push((nx - x) / dy);
}
}
let abandonFactor = +$("#abandonFactor").val();
let newCoordsArray = [];
for(let i=0;i<mArray.length;i++){
let na = newCoordsArray[i] = [];
// calculate the abandonRate base on the amount of the original points
let abandonRate = coordsArray[i].length * abandonFactor;
for(let j=0;j<mArray[i].length-1;j++){
let m = mArray[i][j], // this slope
nm = mArray[i][j+1]; // next slope
let diffRate = Math.abs((m - nm) / m); // calculate the changes of the slope
// check if the diffRate is greater than abandonRate
// or the sign of m not equals the sign of nm
// you can try out removing the "sign check part" and see what would happen ;)
if(diffRate >= abandonRate || (Math.sign(m) !== Math.sign(nm))){
na.push(coordsArray[i][j]);
}
}
}
let newPath = [];
// create new path
for(let i=0;i<newCoordsArray.length;i++){
let temp = [];
for(let j=0;j<newCoordsArray[i].length;j++){
let {x, y} = newCoordsArray[i][j];
let p = `${x},${y}`;
temp.push(p);
}
newPath.push("M" + temp.join("L") + "z");
}
$("#JP-01").attr("d", newPath.join(""));
}).click();
$("#abandonFactor").on("change", function(){
$("#abandonFactor_text").text(`Abandon factor: ${this.value}`);
$("#simplify-2").click();
});
div {
width: 50%;
}
.original {
float: left;
}
.simplified {
float: right;
}
.map {
box-sizing: border-box;
width: 100%;
padding: 0.5rem;
text-shadow: 1px 1px white;
cursor: grab;
}
.land {
fill: lightgreen;
fill-opacity: 1;
stroke: white;
stroke-opacity: 1;
stroke-width: 0.5;
cursor: pointer;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="original">
<svg id="OriginalMap" viewBox="324.55999755859375 0 126.44989013671875 111.65999603271484" class="map" xmlns="http://www.w3.org/2000/svg" xmlns:amcharts="http://amcharts.com/ammap">
<g>
<path id="OriginalJP-01" title="Hokkaido" class="land" d="
M344.04,71.29l2.71,-2.13l0.04,-0.59l-1.96,-3.17l-1.38,-0.99l-0.7,-1.16l0.68,-2.54l-0.27,-0.48l1.64,0.11l0.6,-0.42l0.21,-0.54l0.53,-0.21l2.75,2.66l1.93,0.87l1.13,1.28l2.48,-0.47l0.17,-0.42l1.02,-0.32l0.72,0.02l-0.14,1.17l3.04,1.39l2.2,-1.22l1.97,-2.15l0.99,-1.6l0.1,-2.35l-1.38,-2.76l0.54,-1.94L363,51.89l-0.45,-2.27l1.02,-2.1l1.47,-0.89l0.76,-0.05l2.03,-1.32l0.93,-1.89l0.41,-1.95l-0.35,-7.86l0.5,-0.32l1.64,-3.44l0.21,-2.86l0.38,-0.79l0.13,-1.6l-0.3,-3.27l-0.77,-3.84l-3.04,-7.32l-0.31,-1.16l0.07,-0.99l0.86,-1.27l0.79,-1.95l-0.41,-1.4l0.15,-1.28l0.75,0.77l0.13,0.52l0.39,0.16l1.76,-0.28l1.35,-0.94l0.55,-1.99L374.52,0l0.47,0.33l0.56,1.29l0.59,0.34l0.63,1.47l2.32,1.7l2.21,3.12l4.95,5.56l3.91,7.23l2.58,2.55l2.44,3.1l5.58,4.4l1.57,0.89l0.33,0.95l1.26,0.96l3.7,1.94l3.24,1.28l5.88,1.69l2.79,0.53l0.44,-0.31l0.29,0.77l-0.06,1.28l0.87,1.55l0.58,0.48l3.21,1.01l3.41,0.21l2.57,-0.32l2.28,-1.99l1.54,-1.92l2.18,-2.04l1.83,-1.26l0.66,-1.45l1.1,-1.16l0.78,-1.38l0.46,0.03l0.45,2.23l-1.21,2.49l-0.91,0.92l-0.6,2.4l-1.73,2.13l-0.94,2.12l0.1,0.83L436.06,48l0.22,1.06l1.35,2.58l1.49,1.97l1.52,5.23l1.49,1.96l-1.16,-0.53l-0.19,-0.53l-1.14,-0.22l-0.24,0.37l0.21,1.26l0.86,-0.67l0.47,0.4l-0.29,0.55l0.37,0.39l0.4,-0.28l2.55,0.62l2.09,-1.7l0.32,-0.6l1.11,-0.71l0.24,-0.5l3.28,0.2l-0.19,0.5l-1.17,0.91l-1.73,0.31l-1.87,1.03l-0.8,2.93l-0.77,-0.36l-1.24,-0.05l-2.71,0.51l-1.51,0.9l-1.26,-0.21l-0.62,0.6l-0.14,1.15l-1.6,2.15l-1.02,0.49l-1.9,-0.1l-0.9,-0.79l0.03,-0.53l0.33,-0.31l-0.55,-0.37l-0.73,0.1l-1.19,1.73l-0.02,0.41l1.15,1.13l-2.01,-0.12l-1.15,-0.35l-3.58,-0.02l-0.69,-0.15l-1.58,-1.27l-3.06,0.54l-2.43,1.18l-3.4,2.53l-5.27,5.27l-4.08,5.8l-1.53,3.31l-0.28,1.67l0.55,1.44l-0.82,3.88l-0.91,1.53l0,1.61l-3.7,-3.72l-4.85,-2.02l-8.09,-4.45l-2.38,-1.67l-1.84,-2.05l-3.21,-0.94l-2.21,-2.05l-1.82,-1.2l-1.83,-0.53l-2.38,-0.04l-3.17,1.04l-2.08,1.29l-2.68,2.28l-1.59,0.89l-2.12,2.45l-0.34,0.75l-1.07,-0.4l-0.33,-0.55l0.38,-0.29l0.59,0.64l0.12,-0.81l-1.35,-0.41l-0.87,-2.37l-0.79,-0.4l-1.07,-1.12l-0.12,-0.58l-1.27,-1.23l-1.24,-0.09l-1.25,0.53l-0.97,-0.59l-1.21,0.03l-1.68,1.81l-1.44,2.75l-0.71,2.55l0.28,1.94l2.32,1.2l2.74,2.35l0.75,0.14l1.37,-0.53l2.04,0.31l1.23,2.21l1.63,1.22l1.08,1.88l3.15,1.36l0.55,0.89l0.79,0.59l-0.61,0.61l-0.95,0.01l-1.1,1.3l-1.66,0.66l-0.86,-0.79l-2.78,-0.91l-0.93,0.16l-0.28,0.62l-0.41,0.1l-0.18,-0.61l0.68,-0.45l-0.2,-0.67l-0.59,-0.37l-0.86,0.08l-0.41,0.33l-0.43,1.68l-1.44,1.06l-1.29,0.1l-0.35,0.33l-0.31,3.94l-0.4,0.48l-0.94,0.03l-1.96,0.87l-0.9,1.89l-0.39,0.32l-1.02,-0.8l-1.14,0.22l-0.9,-0.71l-1.08,-2.72l-0.09,-0.86l0.41,-1.77l1.35,-3.03v-1.05l0.77,0.08l0.29,-0.59l0.31,-2.7l-0.4,-1.98l-1.9,-2.94l-0.67,-0.36l-1.3,-0.12L334.29,91l-0.85,-0.88l-1.14,-0.41l-0.39,-0.73l-0.19,-1.39l0.31,-1.12l0.94,-1.03l0.33,-0.95l0.03,-2.65l-0.49,-2.23l0.92,-1.45l1.12,-0.62l2.28,-0.06l0.61,-0.91l1.39,-0.73l0.85,-1.92l0.82,0.57l0.56,0.97l0.89,-0.23l0.09,-1.39l0.82,-0.84L344.04,71.29z
M358.76,9.86l-0.09,-1.23l1.28,-1.25l2.07,1.21l0.51,1.81l-1.81,1.49l-1.37,-1.07L358.76,9.86z
M326.88,91.03l-0.29,2.22l-0.39,0.63l-0.65,0.23l-0.02,0.36l-0.61,-0.54l0.06,-1.71l-0.42,-1.11l0.74,-1.09l1.91,-0.38l0.46,-0.52l0.03,0.64L326.88,91.03z
M357.23,4.25l-0.26,2.59l-0.39,0.13L355.7,4l0.22,-1.31l-0.61,-0.34l0.09,-0.73l0.65,0.85l0.66,-0.15l-0.02,-0.59L357.03,2l0.35,0.78L357.23,4.25z"/>
</g>
</svg> Original
</div>
<div class="simplified">
<svg id="Map" viewBox="324.55999755859375 0 126.44989013671875 111.65999603271484" class="map" xmlns="http://www.w3.org/2000/svg" xmlns:amcharts="http://amcharts.com/ammap">
<g>
<path id="JP-01" title="Hokkaido" class="land" d=""/>
</g>
</svg> Simplified
<button id="simplify-1">Your method</button>
<button id="simplify-2">New method</button>
<input type="range" min="0.0001" max="0.1" value="0.01" step="0.0001" class="slider" id="abandonFactor">
<div id="abandonFactor_text">Abandon factor: 0.01</div>
</div>

Convert a number into sum of two other numbers so the difference is minimum

In Mars, there are only two denominations of currency ,x and y. A
Marsian goes to a bar and the bill is "z". Using x and y he has to pay
the bill. But the bar doesn't tender change, any extra money will be
taken as tips.
So write a function in JavaScript that helps the marsian to reduce the
tips.
The function takes in x, y, z and returns the amount of tip he has to
pay.
Example 1
Input: 2, 5, 109
Output: 0
Explanation: 21 coins of 5, and 2 coins of 2
Example 2
Input: 5, 7, 43
Output: 0
Explanation: 4 coins of 7, and 3 coins of 5
Example 3
Input: 15, 19, 33
Output: 1
Explanation: 1 coin of 15 and 1 coin of 19
Solution: I think this is level one DP problem, something like subset sum. Like for finding the optimal tip for the larger number, knowing the optimal tip for all the below numbers would help.
const coinA = 2
const coinB = 5
const sum = 13
var arr = [];
arr[0] =0;
console.log(getMyTip(coinA, coinB, sum));
function getMyTip(){
for(var i=1; i<= sum; i++){
var minA, minB;
if( i < coinA){
minA = coinA - i;
}else{
minA = arr[i - coinA];
}
if( i < coinB){
minB = coinB - i;
}else{
minB = arr [i - coinB]
}
arr[i] = Math.min(minA, minB);
}
return arr[sum];
}
Jsfiddle: https://jsfiddle.net/7c4sbe46/
But I'm not sure why it is not getting accepted. Please let me know if I'm missing something with the logic here.
It is more related to diophantine equations, i.e. is there a solution to a.x+b.y=z ? The answer is yes if z is a multiple of the greatest common divisor of x and y (called it gcd). If not, your tip will be the difference between 1. the smaller number divisible by gcd and greater than z
and 2. z.
Once you know the value of the tip, you can even easily know the number of x and y that you need by slightly modifying the value of z to (z+tip).
#include <stdio.h>
int main()
{
int curr1, curr2, bill;
scanf("%d %d %d",&curr1,&curr2,&bill);
int gcd, tip=0;
int x=curr1;
int y=curr2;
while(x!=y)
{
if(x > y)
x -= y;
else
y -= x;
}
gcd=x;
if((bill%curr1==0) || (bill%curr2==0) || (bill%(curr1 + curr2)==0)){
tip = 0;
} else if(bill>(curr1 + curr2) && (bill % gcd==0)) {
tip = 0;
} else if((curr1 + curr2) > bill){
if(curr2 > curr1){
tip = (bill % (curr2-curr1));
}else{
tip = (bill % (curr1-curr2));
}
}
printf("%d",tip);
return 0;
}
There is no need to use dp for this. Here is the simple solution -
// x -> first currency denomination
// y -> second currency denomination
// z -> total bill
var calculateTip = function(x,y,z) {
var xMax = Math.floor(z/x);
var tip = y;
if(xMax == 0) {
tip = (x-z) < (Math.ceil(z/y)*y - z) ? (x-z) : (Math.ceil(z/y)*y - z);
}
while (xMax>=0) {
var tempTip = xMax*x + Math.ceil((z-xMax*x)/y)*y - z;
if(tempTip < tip) {
tip = tempTip;
}
xMax--;
}
return tip;
}
var minimumTip = function(x,y,z) {
if(x>y) {
return calculateTip(x,y,z);
} else {
return calculateTip(y,x,z);
}
}
console.log(minimumTip(2, 5, 109));
var findTip = function(x=2, y=5, z=13){
var x = x;
var y = y;
var z = z;
var tip ;
var temp1 = x;
var temp2 = y
function findNumber(num,total){
if(num > total){
return num-total;
}
else{
var q = Math.floor(total/num);
return ((q+1)*num)-total;
}
}
function findMin(a,b,c){
var min ;
if(a<b && a<c){
min = a
}else{
if(b<c){
min = b;
}else{
min = c;
}
}
return min;
}
while(temp1!=temp2)
{
if(temp1 > temp2)
temp1 -= temp2;
else
temp2 -= temp1;
}
var factor =temp1;
if(z%x == 0 || z%y == 0 || z%(x+y) == 0) {
tip = 0;
}else if(z%factor == 0 && z>=x*y - x -y){
tip = 0;
}
else {
var minX= findNumber(x,z);
var minY = findNumber(y,z);
var minXY = findNumber(x+y,z);
console.log(minX,minY,minXY)
tip = findMin(minX,minY,minXY);
}
alert('the tip is '+ tip.toString());
return tip;
}
findTip(21, 11, 109);

Why does this code run slow in firefox?

So I wrote this code for a simple game. The code runs at 60 fps in both Chrome and Safari but Firefox barely manages 30-40 fps. The code looks simple enough to me. What could be causing the delay?
I checked in firebug and found out that only one function "follow" is taking up all the time. Here is the code:
function checkCollision (ball0, ball1) {
var dx = ball1.X - ball0.X,
dy = ball1.Y - ball0.Y,
dist = Math.sqrt(dx * dx + dy * dy);
if (dist < ball0.rad + ball1.rad) {
var angle = Math.atan2(dy, dx),
sin = Math.sin(angle),
cos = Math.cos(angle);
var pos0 = {x: 0, y: 0},
pos1 = rotate(dx, dy, sin, cos, true),
vel0 = rotate(ball0.spdX, ball0.spdY, sin, cos, true),
vel1 = rotate(ball1.spdX, ball1.spdY, sin, cos, true),
vxTotal = vel0.x - vel1.x;
vel0.x = ((ball0.mass - ball1.mass) * vel0.x + 2 * ball1.mass * vel1.x) /
(ball0.mass + ball1.mass);
vel1.x = vxTotal + vel0.x;
var absV = Math.abs(vel0.x) + Math.abs(vel1.x),
overlap = (ball0.rad + ball1.rad) - Math.abs(pos0.x - pos1.x);
pos0.x += vel0.x / absV * overlap;
pos1.x += vel1.x / absV * overlap;
//rotate positions back
var pos0F = rotate(pos0.x, pos0.y, sin, cos, false),
pos1F = rotate(pos1.x, pos1.y, sin, cos, false);
ball1.X = ball0.X + pos1F.x;
ball1.Y = ball0.Y + pos1F.y;
ball0.X = ball0.X + pos0F.x;
ball0.Y = ball0.Y + pos0F.y;
var vel0F = rotate(vel0.x, vel0.y, sin, cos, false),
vel1F = rotate(vel1.x, vel1.y, sin, cos, false);
ball0.spdX = vel0F.x;
ball0.spdY = vel0F.y;
ball1.spdX = vel1F.x;
ball1.spdY = vel1F.y;
}
}
function move()
{
var side,i;
for (i=0 ; i < balls.length; i++)
{
var obj = balls[i];
if (side=obj.edgeX())
{
if (side === 'l')
obj.X = obj.rad;
else if (side === 'r')
obj.X = canvas.width() - obj.rad;
obj.spdX*=-1;
}
if (side=obj.edgeY())
{
if (side === 't')
obj.Y = obj.rad;
else if (side === 'b')
obj.Y = canvas.height() - obj.rad;
obj.spdY*=-1;
}
if (leash == true && i === 0)
{
if (mouse.X>obj.X && (obj.spdX<10))
obj.spdX+=obj.accX;
else if (mouse.X<obj.X && (obj.spdX>-10))
obj.spdX-=obj.accX;
if (mouse.Y>obj.Y && (obj.spdY<10))
obj.spdY+=obj.accY;
else if (mouse.Y<obj.Y && (obj.spdY>-10))
obj.spdY-=obj.accY;
}
obj.X+=obj.spdX;
obj.Y+=obj.spdY;
if (Math.abs(obj.spdX)>0.1)
obj.spdX*=0.98;
else obj.spdX=0;
if (Math.abs(obj.spdY)>0.1)
obj.spdY*=0.98;
else obj.spdY = 0;
};
}
function follow()
{
var ballA, i, ballB,j;
requestAnimationFrame(follow);
//stats.begin();
context.clearRect(0,0,canvas.width(),canvas.height());
move();
for (i = 0, len = balls.length - 1; i < len; i++) {
ballA = balls[i];
for (j = i + 1; j < balls.length; j++) {
ballB = balls[j];
checkCollision(ballA, ballB);
}
}
balls.forEach(function(obj){
drawCircle(obj.X,obj.Y,obj.rad, obj.color);
if (leash == true && obj.control === true)
{drawLeash(mouse.X,mouse.Y,obj.X,obj.Y,obj.color);}
});
//stats.end();
};
Here is the animation: http://ipsumturpis.xtreemhost.com/follower/index.html
I vaguely remembered there used to be a problem in FF regarding with canvas drawing performance, so I have commented out drawCircle(obj.X,obj.Y,obj.rad, obj.color); and poof, magic happened - my frame rate went up from 11 FPS to 60.
Try caching balls length in a variable. Unless it’s absolutely necessary for reasons I don’t see, running balls.length (or any function) in every iteration of a loop is naturally going to be time consuming.
So try something like this;
ballslen = balls.length;
for (j = i + 1; j < ballslen; j++)

Categories

Resources