How to get linear interpolated points among several control points? - javascript

For example I have 3 points: (y:0, x:0), (y:100, x:10), (y:50, x:100). So I need to get 10 points among this polyline the distance between those is equal. I know how to get points between 2 ones, but I defenitely don't know how to receive among several points.
For receiving distance between 2 points I use this:
function getDistance(y1,x1,y2,x2){
return Math.sqrt(Math.pow(y2-y1, 2) + Math.pow(x2-x1, 2))
}
For computing single points I use it:
function interpolate(a, b, frac){
return {
y: a.y+(b.y-a.y)*frac,
x: a.x+(b.x-a.x)*frac
};
}
Can anyone help me?

This is working fine (for the example I'm using 3 points on the same line, but it should work for every combination)
function getDistance({y: y1, x:x1}, {y:y2, x:x2}){
return Math.sqrt(Math.pow(y2-y1, 2) + Math.pow(x2-x1, 2))
}
function interpolate(a, b, frac){
return {
x: a.x+(b.x-a.x)*frac,
y: a.y+(b.y-a.y)*frac,
};
}
//generate N point in a line
function generateOnLineEveryDistance(a, b, n, distance){
let res = [];
for(let i = 0; i < n ; i++){
// add a point interpolating on a line after (i + 1) * distance from the beginning point (i+1 to avoid the starting point 0,0)
res.push(interpolate(a, b, (i + 1) * distance))
}
return res;
}
function generate(points, n){
// calculate total distance to find out how distant we have to place the points
let totalDistance = 0;
for(let i = 1; i < points.length; i++){
totalDistance += getDistance(points[i - 1], points[i])
}
// distance to place the points
const pointDistance = totalDistance / (n - 1);
// the first point is always included
let res = [points[0]];
// now, we will consider a segment as point[i - 1] & point[i], and we will consider only the piece where we can fit point:
// eg. in a segment long 10 ([x:0, y:0], [x:0, y:10]), and a pointDistance of 4, we will consider only [x:0, y:0], [x:0, y:8]
// and the remainder is 2... this remainder will be used in the next segment, "appending it at the beginning"
// let's say the second segment is [x:0, y:10], [x:0, y:20], with the remainder it will be [x:0, y:8], [x:0, y:20]
let remainder = 0;
for(let i = 1; i < points.length ; i++){
// add the remainder if exists at the beginning of the current segment (point[i-1], point[i])
// source https://stackoverflow.com/questions/7740507/extend-a-line-segment-a-specific-distance
if(remainder > 0){
let a = points[i];
let b = points[i - 1];
let lengthAB = Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2))
points[i - 1].x = b.x + (b.x - a.x) / lengthAB * remainder;
points[i - 1].y = b.y + (b.y - a.y) / lengthAB * remainder;
}
// points we need to generate
let nPoints = Math.floor(getDistance(points[i - 1], points[i]) / pointDistance)
// remainder to add to the next iteration
remainder = getDistance(points[i - 1], points[i]) - nPoints * pointDistance;
// add to the result the points
res = [
...res, // previous result
...generateOnLineEveryDistance( // new points
points[i - 1], // first point to consider
points[i], // second point to consider
nPoints, // number of points to generate
pointDistance / getDistance(points[i - 1], points[i]) // the ratio we want to use to generate them
)
]
}
// small fix in case of .333333 decimals that will "miss" the last point because it's .9999 and not 1.00000
if(res.length < n){
res = [...res, points[points.length - 1]]
}
return res;
}
const points = [
{
x: 0,
y: 0
} , {
x: 0,
y: 10
} , {
x: 0,
y: 20
}
]
console.log(generate(points, 4))
however, I can see this library already doing it, I've not checked it out, but maybe is worth checking it out because the code I'm providing is pretty untested and unreadable
UPDATE:
I've tested it against the examples they are providing and it's giving back the same result, so GG

Related

Which algorithm should be used to find the point?

You need to find some unknown, predetermined point in three-dimensional space, in the smallest number of attempts, using only a function that can return the distance from
any point you pass to it to the desired unknown point.
To solve the problem, first implement a function f that, by taking the coordinates of any point s(x, y, z), return the distance between that point and a conditionally unknown, randomly generated point
point you arbitrarily generate r(x, y, z), where x, y, z can be integers between
0 и 100.
For example, for an arbitrarily generated point r(0, 0, 10) and a point passed to the function
s(0, 0, 0), the result of the function would be as follows:
f(s) = 10 // the distance between s(0, 0, 0) and r(0, 0, 10) is 10
Next, implement the algorithm itself for the assignment. The algorithm should find the coordinates of
of an arbitrarily generated point with the least number of calls to the function f.
I have a randomizer instead of an algorithm, that's all I got. Help.
const pointToFound = {
x: 12,
y: 9,
z: 76,
};
let attemts = 0;
let isXFound = false;
let isYFound = false;
let isZFound = false;
const pointHistory = [];
const getRandomPoint = () => {
return {
x: isXFound ? isXFound : Math.floor(Math.random() * 101),
y: isYFound ? isYFound : Math.floor(Math.random() * 101),
z: isZFound ? isZFound : Math.floor(Math.random() * 101),
};
};
const getDifference = (point, pointToCompare) => {
return {
x:
Math.max(point.x, pointToCompare.x) - Math.min(point.x, pointToCompare.x),
y:
Math.max(point.y, pointToCompare.y) - Math.min(point.y, pointToCompare.y),
z:
Math.max(point.z, pointToCompare.z) - Math.min(point.z, pointToCompare.z),
};
};
const condition = !isXFound && !isYFound && !isZFound;
while (condition) {
const point = getRandomPoint();
const difference = getDifference(point, pointToFound);
pointHistory.push(point);
attemts += 1;
if (isXFound && isYFound && isZFound) {
console.log("Total attempts: ", attemts);
console.log(point);
break;
}
if (difference.x === 0 && !isXFound) {
isXFound = point.x;
}
if (difference.y === 0 && !isYFound) {
isYFound = point.y;
}
if (difference.z === 0 && !isZFound) {
isZFound = point.z;
}
}
console.log(pointHistory);
I have a randomizer instead of an algorithm, that's all I got. Help.
This can be done with at most 3 guesses and often with 2 guesses:
Let the first guess be [0, 0, 0], and ask for the distance
Find in the 100x100x100 cube all points that have that distance to [0, 0, 0]. There might be around 100-200 points that have that distance: consider all of these candidates.
Take the first candidate as the second guess and ask for the distance
Find among the other candidates the ones that have exactly that distance to the first candidate. Often there will be only one point that satisfies this condition. In that case we can return that candidate and only 2 guesses were necessary.
Otherwise (when there is more than one candidate remaining) repeat the previous step which will now certainly lead to a single point.
Here is an implementation that provides a blackbox function which chooses the secret point in a local variable, and which returns two functions: f for the caller to submit a guess, and report for the caller to verify the result of the algorithm and report on the number of guesses. This is not part of the algorithm itself, which is provided in the findPoint function.
const rnd = () => Math.floor(Math.random() * 101);
const distance = (a, b) =>
a.reduce((acc, x, i) => acc + (x - b[i]) ** 2, 0) ** 0.5;
function findPoint(f) {
// First guess is always the zero-point
let guess = [0, 0, 0];
let dist = f(guess);
if (dist === 0) return guess; // Extremely lucky!
// Find the points in the cube that have this distance to [0,0,0]
let candidates = [];
const limit = Math.min(100, Math.round(dist));
for (let x = 0; x <= limit; x++) {
const p = [x, limit, 0];
// Follow circle in X=x plane
while (p[1] >= 0 && p[2] <= limit) {
const d = distance(p, guess);
const diff = d - dist;
if (Math.abs(diff) < 1e-7) candidates.push([...p]);
if (diff >= 0) p[1]--;
else p[2]++;
}
}
// As long there are multiple candidates, continue with a guess
while (candidates.length > 1) {
const candidates2 = [];
// These guesses are taking the first candidate as guess
guess = candidates[0];
dist = f(guess);
if (dist === 0) return guess; // lucky!
for (const p of candidates) {
let d = distance(p, guess);
let diff = d - dist;
if (Math.abs(diff) < 1e-7) candidates2.push(p);
}
candidates = candidates2;
}
return candidates[0]; // Don't call f as we are sure!
}
function blackbox() {
const secret = [rnd(), rnd(), rnd()];
console.log("Secret", JSON.stringify(secret));
let guessCount = 0;
const f = guess => {
guessCount++;
const dist = distance(secret, guess);
console.log("Submitted guess " + JSON.stringify(guess) + " is at distance " + dist);
return dist;
};
const report = (result) => {
console.log("Number of guesses: " + guessCount);
console.log("The provided result is " + (distance(secret, result) ? "not" : "") + "correct");
}
return {f, report};
}
// Example run
const {f, report} = blackbox();
const result = findPoint(f);
console.log("Algorithm says the secret point is: " + JSON.stringify(result));
report(result);
Each run will generate a new secret point. When running this thousands of times it turns out that there is 1/9 probability that the algorithm needs a third guess. In the other 8/9 cases, the algorithm needs two guesses.
One idea is as follows:
You pick an initial random point, and for each dimension, find the exact value. How? For the sake of symmetry, suppose that you desire to find x of the target point. Increase by one the x, and compute the distance of the new point from the target point. If it goes further, it means that you should move in the opposite direction. Hence, you can run a binary search and get the distance to find the exact x of the target point. Otherwise, it means that you are going in the right direction along X-axis. So, do a binary search between all points with the same y and z such that their x values can change from x+1 to 100. A more formal solution comes in the following (just a pseudo-code).
You should also ask about the complexity of this solution. As the dimension of the point is constant (3) and checking these conditions take a constant time, the complexity of number of calling getDifference function is O(log(n)). What is n here? the length of valid range for coordinates (here is 100).
1. p: (x,y,z) <- Pick a random coordinate
2. dist: (dist_x, dist_y, dist_z) <- getDifference(p, TargetPoint)
3. For each dimension, do the following (i can be 0 (x), 1 (y), 2 (3)):
4. if(dist == 0):
5. isFound[i] <- p[i]
6. continue
7. new_i <- p[i] + 1
8. new_point <- p
9. new_point[i] <- new_i
10. new_dist <- getDifference(new_point, pointToFound)
11. if(new_dist == 0):
12. isFound[i] <- new_point[i];
13. continue
14. if(new_dist[i] > dist[i]):
15. isFound[i] <- binary_search for range [0, p[i]-1] to find the exact value of the pointToFound in dimension i
15. continue
16. else:
17. isFound[i] <- binary_search for range [p[i] + 1, 100] to find the exact value of the pointToFound in dimension i
18. continue
Following method will work for coordinates with positive or negative real values as well.
Let's say you are searching for the coordinates of point P. As the first query point, use origin O. Let the distance to the origin O be |PO|. At this point, you know that P is on the surface of sphere
(P.x)^2 + (P.y)^2 + (P.z)^2 = |PO|^2 (1)
As the second query point, use Q = (|PO|, 0, 0). Not likely but if you find the distance |PQ| zero, Q is the point you are looking for. Otherwise, you get another sphere equation, and you know that P is on the surface of this sphere as well:
(P.x - |PO|)^2 + (P.y)^2 + (P.z)^2 = |PQ|^2 (2)
Now, if you subtract (1) from (2), you get
(P.x - |PO|)^2 - (P.x)^2 = |PQ|^2 - |PO|^2 (3)
Since the only unknown in this equation is P.x you can get its value:
P.x = (((-|PQ|^2 + |PO|^2) / |PO|) + |PO|)/2)
Following similar steps, you can get P.y with R = (0, |PO|, 0) and P.z with S = (0, 0, |PO|). So, by using four query points O, Q, R, and S you can get the coordinates of P.

Isometric topological sort issue

I've just implemented a topological sort algorithm on my isometric game using this guide: https://mazebert.com/2013/04/18/isometric-depth-sorting/
The issue
Here's a little example (this is just a drawing to illustrate my problem because as we say, a picture is worth a thousand words), what I'm expecting is in left and the result of the topological sorting algorithm is in right
So in the right image, the problem is that the box is drawn BEFORE the character and I'm expecting it to be drawn AFTER like in the left image.
Code of the topological sorting algorithm (Typescript)
private TopologicalSort2() {
// https://mazebert.com/2013/04/18/isometric-depth-sorting/
for(var i = 0; i < this.Stage.children.length; i++) {
var a = this.Stage.children[i];
var behindIndex = 0;
for(var j = 0; j < this.Stage.children.length; j++) {
if(i == j) {
continue;
}
var b = this.Stage.children[j];
if(!a.isoSpritesBehind) {
a.isoSpritesBehind = [];
}
if(!b.isoSpritesBehind) {
b.isoSpritesBehind = [];
}
if(b.posX < a.posX + a.sizeX && b.posY < a.posY + a.sizeY && b.posZ < a.posZ + a.sizeZ) {
a.isoSpritesBehind[behindIndex++] = b;
}
}
a.isoVisitedFlag = 0;
}
var _sortDepth = 0;
for(var i = 0; i < this.Stage.children.length; ++i) {
visitNode(this.Stage.children[i]);
}
function visitNode(n: PIXI.DisplayObject) {
if(n.isoVisitedFlag == 0) {
n.isoVisitedFlag = 1;
if(!n.isoSpritesBehind) {
return;
}
for(var i = 0; i < n.isoSpritesBehind.length; i++) {
if(n.isoSpritesBehind[i] == null) {
break;
} else {
visitNode(n.isoSpritesBehind[i]);
n.isoSpritesBehind[i] = null;
}
}
n.isoDepth = _sortDepth++;
}
}
this.Stage.children.sort((a, b) => {
if(a.isoDepth - b.isoDepth != 0) {
return a.isoDepth - b.isoDepth;
}
return 0;
});
}
Informations
Player:
posX: [the x coordinate of the player]
posY: [the y coordinate of the player]
posZ: 0
sizeX: 1
sizeY: 1
sizeZ: 1
Box:
posX: [the x coordinate of the box]
posY: [the y coordinate of the box]
posZ: 0
sizeX: 3
sizeY: 1
sizeZ: 1
X and Y axis
Do you have any idea of the source of this problem? and maybe how to solve it?
The way to determine whether one object is before the other requires a bit more linear algebra.
First of all, I would suggest to translate the coordinates from the "world" coordinates to the "view" 2D coordinates, i.e. to the rows and columns of the display.
Note also that the original Z coordinate does not influence the sort order (imagine that an object would be lifted up along the Z axis: we can find a sort order where this move would not have any impact). So the above-mentioned translation could assume all points are at Z=0.
Let's take this set-up, but depicted from "above", so when looking along the Z axis down to the game floor:
In the picture there are 7 objects, numbered from 0 to 6. The line of view in the game would be from the bottom-left of this picture. The coordinate system in which I would suggest to translate some points is depicted with the red row/col axis.
The white diagonals in each object link the two points that would be translated and used in the algorithm. The assumption is that when one object is in front of another, their diagonal lines will not intersect. If they would, it would mean that objects are overlapping each other in the game world, which would mean they are like gasses, not solids :) I will assume this is not the case.
One object A could be in front of another object B when in the new coordinate system, the left-most column coordinate of B falls between the two column coordinates of A (or vice versa). There might not really be such an overlap when their Z coordinates differ enough, but we can ignore that, because when there is no overlap we can do no harm in specifying a certain order anyway.
Now, when the coordinates indicate an overlap, the coordinates of diagonals (of A and B) must be compared with some linear algebra formula, which will determine which one is in front of the other.
Here is your adapted function that does that:
topologicalSort() {
// Exit if sorting is a non-operation
if (this.Stage.children.length < 2) return;
// Add two translated coordinates, where each of the resulting
// coordinates has a row (top to bottom) and column
// (left to right) part. They represent a position in the final
// rendered view (the screen).
// The two pairs of coordinates are translations of the
// points (posX + sizeX, Y, 0) and (posX, posY + sizeY, 0).
// Z is ignored (0), since it does not influence the order.
for (let obj of this.Stage.children) {
obj.leftCol = obj.posY - obj.posX - obj.sizeX;
obj.rightCol = obj.posY - obj.posX + obj.sizeY;
obj.leftRow = obj.posY + obj.posX + obj.sizeX;
obj.rightRow = obj.posY + obj.posX + obj.sizeY;
obj.isoSpritesBehind = [];
}
for(let i = 0; i < this.Stage.children.length; i++) {
let a = this.Stage.children[i];
// Only loop over the next objects
for(let j = i + 1; j < this.Stage.children.length; j++) {
let b = this.Stage.children[j];
// Get the two objects in order of left column:
let c = b.leftCol < a.leftCol ? b : a;
let d = b.leftCol < a.leftCol ? a : b;
// See if they overlap in the view (ignoring Z):
if (d.leftCol < c.rightCol) {
// Determine which is behind: some linear algebra
if (d.leftRow <
(d.leftCol - c.leftCol)/(c.rightCol - c.leftCol)
* (c.rightRow - c.leftRow) + c.leftRow) {
// c is in front of d
c.isoSpritesBehind.push(d);
} else { // d is in front of c
d.isoSpritesBehind.push(c);
}
} // in the else-case it does not matter which one comes first
}
}
// This replaces your visitNode function and call:
this.Stage.children.forEach(function getDepth(obj) {
// If depth was already assigned, this node was already visited
if (!obj.isoDepth) {
// Get depths recursively, and retain the maximum of those.
// Add one more to get the depth for the current object
obj.isoDepth = obj.isoSpritesBehind.length
? 1+Math.max(...obj.isoSpritesBehind.map(getDepth))
: 1; // Depth when there is nothing behind it
}
return obj.isoDepth; // Return it for easier recursion
});
// Sort like you did, but in shorter syntax
this.Stage.children.sort((a, b) => a.isoDepth - b.isoDepth);
}
I add a snippet where I completed the class with a minimum of code, enough to make it run and output the final order in terms of object index numbers (as they were originally inserted):
class Game {
constructor() {
this.Stage = { children: [] };
}
addObject(posX, posY, posZ, sizeX, sizeY, sizeZ) {
this.Stage.children.push({posX, posY, posZ, sizeX, sizeY, sizeZ,
id: this.Stage.children.length}); // add a unique id
}
topologicalSort() {
// Exit if sorting is a non-operation
if (this.Stage.children.length < 2) return;
// Add two translated coordinates, where each of the resulting
// coordinates has a row (top to bottom) and column
// (left to right) part. They represent a position in the final
// rendered view (the screen).
// The two pairs of coordinates are translations of the
// points (posX + sizeX, Y, 0) and (posX, posY + sizeY, 0).
// Z is ignored (0), since it does not influence the order.
for (let obj of this.Stage.children) {
obj.leftCol = obj.posY - obj.posX - obj.sizeX;
obj.rightCol = obj.posY - obj.posX + obj.sizeY;
obj.leftRow = obj.posY + obj.posX + obj.sizeX;
obj.rightRow = obj.posY + obj.posX + obj.sizeY;
obj.isoSpritesBehind = [];
}
for(let i = 0; i < this.Stage.children.length; i++) {
let a = this.Stage.children[i];
// Only loop over the next objects
for(let j = i + 1; j < this.Stage.children.length; j++) {
let b = this.Stage.children[j];
// Get the two objects in order of left column:
let c = b.leftCol < a.leftCol ? b : a;
let d = b.leftCol < a.leftCol ? a : b;
// See if they overlap in the view (ignoring Z):
if (d.leftCol < c.rightCol) {
// Determine which is behind: some linear algebra
if (d.leftRow <
(d.leftCol - c.leftCol)/(c.rightCol - c.leftCol)
* (c.rightRow - c.leftRow) + c.leftRow) {
// c is in front of d
c.isoSpritesBehind.push(d);
} else { // d is in front of c
d.isoSpritesBehind.push(c);
}
} // in the else-case it does not matter which one comes first
}
}
// This replaces your visitNode function and call:
this.Stage.children.forEach(function getDepth(obj) {
// If depth was already assigned, this node was already visited
if (!obj.isoDepth) {
// Get depths recursively, and retain the maximum of those.
// Add one more to get the depth for the current object
obj.isoDepth = obj.isoSpritesBehind.length
? 1+Math.max(...obj.isoSpritesBehind.map(getDepth))
: 1; // Depth when there is nothing behind it
}
return obj.isoDepth; // Return it for easier recursion
});
// Sort like you did, but in shorter syntax
this.Stage.children.sort((a, b) => a.isoDepth - b.isoDepth);
}
toString() { // Just print the ids of the children
return JSON.stringify(this.Stage.children.map( x => x.id ));
}
}
const game = new Game();
game.addObject( 2, 2, 0, 1, 1, 1 );
game.addObject( 1, 3, 0, 3, 1, 1 );
game.addObject( 6, 1, 0, 1, 3, 1 );
game.addObject( 9, 3, 0, 1, 1, 1 );
game.addObject( 5, 3, 0, 1, 3, 1 );
game.addObject( 7, 2, 0, 1, 1, 1 );
game.addObject( 8, 2, 0, 3, 1, 1 );
game.topologicalSort();
console.log(game + '');
The objects in the snippet are the same as in the picture with the same numbers. The output order is [0,1,4,2,5,6,3] which is the valid sequence for drawing the objects.

Proper way to return multiple variable from a javascript object

I'm currently learning more about the inner workings of javascript objects and came across an example where multiple variables were returned from the object:
var squareGrid = function(width, n, z) {
width = width || 1;
n = n || 4;
var z = z || width / 2.0;
var edges = [],
pts = [];
var step = width / n;
for (var u = 0; u < n; u++) {
var u_added = u * n * 4;
for (var v = 0; v < n; v++) {
var delta = u_added + 4 * v;
var t_v = step * (v - n / 2);
var t_u = step * (u - n / 2);
pts.push([t_v, t_u, z]); // top left
pts.push([t_v, t_u + step, z]); // top right
pts.push([t_v + step, t_u + step, z]); // bottom right
pts.push([t_v + step, t_u, z]); // bottom left
edges.push([delta + 0, delta + 1]);
edges.push([delta + 1, delta + 2]);
edges.push([delta + 2, delta + 3]);
edges.push([delta + 3, delta + 0]);
}
}
return {
edges: edges,
pts: pts
};
}
In this case, in order to return both edges and points, it looks like there are key-value pairs being returned where the keys and values are the same thing. Is this method necessary or can the following just be done?
return{ edges, pts}};
You would be returning an object where the keys would be edges and the values would be the return value of edges.
x = squareGrid(400, 2, 5)
x.edges
// edges
x.pts
//pts
You could do something like: data = callfunction() -> return [edges, points]
data[0]
// edges
data[1]
//points
** Correction **
when keys are stored in a js hash it serializes the key before attempting to store the value
what would get stored is the serialized version of the array as the key, with the value being the array

jquery: sort 4 points to (top-left, top-right, bottom-right, bottom-left)

given: 2 arrays, each containing the coordinates of 4 points:
point_array_a = [point_a_1_x, point_a_1_y, point_a_2_x, ..., point_a_4_y]
point_array_b = [point_b_1_x, ... ..., point_b_4_y]
task:
sort point_array_a such that in the end the points are listed in the following order:
point_array_a_sorted = [top-left_x, top_left_y, top-right_x, top-right_y, bottom-right_x, bottom_right_y, bottom-left_x, bottom_left_y]
sort point_array_b in the same way, such that point_a_k_l corresponds to point_b_k_l like in the beginning.
I'm afraid there is no simple algorithm for this. But the following snippet will do the job (assuming that points with larger y-coordinates lie lower than points with lower y-coordinates):
var i, points = [], leftX = point_array_a[0], topY = point_array_a[1];
for (i = 0; i < 4; i++)
{
leftX = Math.min(leftX, point_array_a[i * 2]);
topY = Math.min(topY, point_array_b[i * 2]);
points.push([
[point_array_a[i * 2], point_array_a[i * 2 + 1]],
[point_array_b[i * 2], point_array_b[i * 2 + 1]]
]);
}
points.sort(function(first, second){
if (first[0][0] == leftX)
return first[0][1] == topY ? -1 : 1;
if (second[0][0] == leftX)
return second[0][1] == topY ? 1 : -1;
return first[0][1] < second[0][1] ? -1 : 1;
});
var point_array_a_sorted = [], point_array_b_sorted = [];
for (i = 0; i < 4; i++)
{
point_array_a_sorted.push(points[i][0][0], points[i][0][1]);
point_array_b_sorted.push(points[i][1][0], points[i][1][1]);
}
We leverage the existing Array.sort function by feeding it just the right objects to compare and swap — pairs of points.

Runtime Error & Time Complexity Issues: Minimize the value |(A[0] + ... + A[P-1]) - (A[P] + ... + A[N-1])|

I recently tackled a coding problem. I came up with a solution to the following problem.
A non-empty zero-indexed array A consisting of N integers is given. Array A represents numbers on a tape.
Any integer P, such that 0 < P < N, splits this tape into two non-empty parts: A[0], A[1], ..., A[P − 1] and A[P], A[P + 1], ..., A[N − 1].
The difference between the two parts is the value of: |(A[0] + A[1] + ... + A[P − 1]) − (A[P] + A[P + 1] + ... + A[N − 1])|
In other words, it is the absolute difference between the sum of the first part and the sum of the second part.
For example, consider array A such that:
A[0] = 3
A[1] = 1
A[2] = 2
A[3] = 4
A[4] = 3
We can split this tape in four places:
P = 1, difference = |3 − 10| = 7
P = 2, difference = |4 − 9| = 5
P = 3, difference = |6 − 7| = 1
P = 4, difference = |10 − 3| = 7
Write a function:
function solution(A);
that, given a non-empty zero-indexed array A of N integers, returns the minimal difference that can be achieved.
For example, given:
A[0] = 3
A[1] = 1
A[2] = 2
A[3] = 4
A[4] = 3
the function should return 1, as explained above.
Assume that:
N is an integer within the range [2..100,000];
each element of array A is an integer within the range [−1,000..1,000].
Complexity:
expected worst-case time complexity is O(N);
expected worst-case space complexity is O(N), beyond input storage (not counting the storage required for input arguments).
Elements of input arrays can be modified.
The following is the feedback I obtained from testing the solution:
CORRECTNESS:
small_range range sequence, length = ~1,000 1.900 s RUNTIME ERROR
tested program terminated unexpectedly
PERFORMANCE:
Detected time complexity: O(N * N)
So I am getting one runtime error for ranges around 1000. And most importantly, I am not getting O(n). I am getting O(n * n) as I am using nested for loops.
(1) How could I fix the runtime error?
(2) How could one construct O(n) algorithm for the same problem? Any suggestions?
This is my solution:
function solution(A){
var len = A.length;
var diff = []; // Array to store the differences
var sumLeft = 0; // Sum of array elements from index 0 to index p - 1
var sumRight = 0; // Sum of array elements from index p to index n - 1
for(var p = 1; p < len; p++){
sumLeft = 0;
sumRight = 0;
// Calculate sumLeft:
for(var i = 0; i < p; i++){
sumLeft += A[i];
}
// Calculate sumRight:
for(var j = p; j < len; j++){
sumRight += A[j];
}
// Calculate differences, compute absolute values, and push into diff array:
diff.push(Math.abs(sumLeft - sumRight));
}
// Return the minimum of diff array by sorting it and returning the first element:
return bubbleSort(diff)[0];
}
function bubbleSort(array){
var len = array.length;
for(var i = 0; i < len; i++){
for(var j = i + 1; j < len; j++){
if(array[i] > array[j]){
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
}
return array;
}
Let me try to explain you how you can think about improving the space and time complexity of your algorithm. You realize where clearly that you're using nested for loops and it greatly increases the iterations and might also be causing the run-time error for sufficiently large inputs.
The first step should be reducing the redundancy of your operations. Now you calculate the left and right sums repeatedly for different values of p. You don't need that at all. I'll give you an example for how the algorithm will flow:
Array indices -> A [0, 1, 2, 3, ....,p ,p+1, ....n-1] for a size n array
At any point A[p] would act as a pivot as it breaks the array into two.
For p = 1, You just take the first element i.e A[0] and the right part of the sum is
A[1] + A[2] + .... A[n-1]
Let S1 = A[0] and S2 = A[1] + A[2] + .... A[n-1] for p = 1
The pivot or the break point here is A[p] i.e A[1]
Calculate the absolute difference |S1- S2| and store it in a variable min-diff
For p = 2,
S1 will simply be S1 + A[1] i.e the previous value of S1 including the last pivot
S2 = S2 - A[1], as we have moved on to the next element.
The sum of the remaining elements would not account the element we just crossed.
Formally,
S1 = S1 + A[p-1] and S2 = S2 - A[p-1]
Calculate the new difference i.e |S1 - S2| and just check
if it is smaller than the value of our variable min-diff.
If it is, update the value of min-diff with the present difference,
otherwise move on to the next element.
At any value of p, S1 represents sum of left half,
S2 represents sum of right half and
min-diff represents the minium absolute difference till now.
Complexity for this algorithm
Time complexity
The only time we calculate the sum of the elements is the first time
when we calculate A[1]+...A[n-1]. After that we just traverse the
elements of the array one by one.
So we traverse the elements of the array at max twice. So time
complexity is clearly O(N)
Space complexity
We use three extra variables i.e S1, S2 and min-diff all through
this algorithm to accumulate the sum and store the minimum absolute
difference along with the value of p and the n elements of the
array.
So space complexity of this algorithm is again O(N)
On a side note- Although you don't require sorting for this problem at all since you're to output the minimum difference only, but whenever sorting, please don't use bubble-sort at it is clearly the least efficient sorting method. You're better off with merge sort or quick sort which have a run time of O(NlogN)
I hope I was able to explain myself. Try to code this into a simple function, shouldn't take long. It should probably fix the run-time error as well.
You don't need to calculate the sum of the vector pieces when you test a new value of P. If you calculated leftSum and rightSum for both parts for P=(p-1), when you have to calculate it for P=p you just need to:
Remove array[p] from rightSum; and
Add array[p] to leftSum.
This both are O(1). If you do it (n-1) times, you are still under O(n) complexity.
Hope that helps.
Code with java : O(N)
import java.math.*;
class Solution {
public int solution(int[] A) {
long sumright = 0;
long sumleft = 0;
long ans;
for (int i =1;i<A.length;i++)
{
sumright += A[i];
}
sumleft = A[0];
ans =Math.abs(sumright+sumleft);
for (int P=1; P<A.length; P++)
{
if (Math.abs(sumleft - sumright)<ans)
{
ans = Math.abs(sumleft - sumright);
}
sumleft += A[P];
sumright -=A[P];
}
return (int) ans;
}
}
Without debugging, this solution gets a 100% task score on Codility (with 100% for both correctness and performance):
function solution(A) {
var sum_right = 0;
for (int of A.slice(1)) {
sum_right += int;
}
var sum_left = A[0];
var diff_of_sums = sum_left - sum_right;
var lowest_diff = Math.abs(diff_of_sums);
var diff_new;
// we assume the length is at least 2
if (A.length == 2) {
return lowest_diff;
}
for (var int of A.slice(1)) {
diff_new = Math.abs(sum_left - sum_right);
if (diff_new < lowest_diff) {
lowest_diff = diff_new;
}
sum_left += int;
sum_right -= int;
}
return lowest_diff;
}
With debugging:
// you can write to stdout for debugging purposes, e.g.
// console.log('this is a debug message');
function solution(A) {
var sum_right = 0;
for (int of A.slice(1)) {
sum_right += int;
}
var sum_left = A[0];
var diff_of_sums = sum_left - sum_right;
// var total = Math.abs(sum_left + sum_right);
var lowest_diff = Math.abs(diff_of_sums);
var diff_new;
// we assume the length is at least 2
if (A.length == 2) {
return lowest_diff;
}
// console.log("lowest_diff initially:", lowest_diff)
// var diff_of_sums_new = diff_of_sums;
// console.log("diff_of_sums initially:", diff_of_sums)
// console.log("A.slice(1):", A.slice(1))
for (var int of A.slice(1)) {
// console.log("lowest_diff", lowest_diff)
diff_new = Math.abs(sum_left - sum_right);
if (diff_new < lowest_diff) {
lowest_diff = diff_new;
}
sum_left += int;
sum_right -= int;
}
// if (Math.abs(sumleft - sumright)<ans)
// {
// ans = Math.abs(sumleft - sumright);
// }
// sumleft += A[P];
// sumright -=A[P];
// // console.log("int === -1:", int === -1);
// // diff_of_sums = diff_of_sums_new;
// console.log("lowest_diff =", lowest_diff);
// // console.log("A[index + 1] =", A[parseInt(index) + 1]);
// // console.log("parseInt(index) === 1", parseInt(index) === 1)
// diff_of_sums = Math.abs(lowest_diff - 2 * Math.abs(int));
// // console.log("diff_of_sums =", diff_of_sums);
// // console.log("diff_of_sums = Math.abs(diff_of_sums - 2 * A[index + 1]) = ", diff_of_sums_new);
// if (diff_of_sums < lowest_diff) {
// lowest_diff = diff_of_sums;
// // console.log("lowest_diff = diff_of_sums =", diff_of_sums_new)
// } else {
// return lowest_diff;
// }
// }
// console.log("lowest_diff before returning", lowest_diff);
return lowest_diff;
}
// Note that it's better to use test cases in Codility for this, but I've left here to show some.
// console.log("solution([-1000, 1000])", solution([-1000, 1000]));
// console.log("solution([2, 7, 20, 30, 1])", solution([2, 7, 20, 30, 1])); // sum 60, smallest diff = |29 - 31| = 2
// console.log("solution([-2, -7, -20, -30, -1])", solution([-2, -7, -20, -30, -1])); // sum -60, smallest diff = 2
// console.log("solution([-1, -1]):", solution([-1, -1]));
// console.log("solution([-2, -1]):", solution([-2, -1]));
// console.log("solution([-2, -1, -3]):", solution([-2, -1, -3]));
// console.log("solution([]):", solution([]))
Initially I tried starting from halfway, but this made the implementation more complicated. This is what I came up with before I ditched that approach (and I can't be bothered with hacking on a solution):
function solution(A) {
// const sum = A.reduce((partial_sum, a) => partial_sum + a);
// console.log(sum);
var size = A.length;
if (size % 2 == 0) {
mid = size/2;
} else {
mid = Math.floor(size/2);
}
console.log("mid initially", mid);
var sum1 = A.slice(0, mid).reduce((partial_sum, a) => partial_sum + a);
// console.log("sum1:",sum1);
var sum2 = A.slice(mid).reduce((partial_sum, a) => partial_sum + a);
// console.log("sum2:", sum2);
var sum_diff = sum1 - sum2;
// console.log("sum_diff:", sum_diff);
if (sum_diff === 0) {
return sum_diff;
}
// sum_diff = function() {Math.abs(sum2 - sum1)};
// sum_diff = sum_diff();
var lowest_diff = Math.abs(sum_diff);
var diff_negative = (sum_diff < 0);
console.log("diff_negative initially:", diff_negative)
var crossed_over = false;
var sum_diff_new;
while (diff_negative) {
mid++;
if (mid === size) {
return lowest_diff;
}
// var sum1_new = sum1 + A[mid];
// var sum2_new = sum2 - A[mid];
// sum_diff_new = sum1_new - sum2_new = sum1 - sum2 + 2*A[mid] = sum_diff - 2*A[mid];
sum_diff_new = sum_diff - 2*A[mid];
diff_negative = (sum_diff_new < 0);
if (diff_negative = false) {
crossed_over = true;
if (lowest_diff <= sum_diff_new) {
return lowest_diff;
} else {
return sum_diff_new;
}
}
}
while(!diff_negative) {
mid--;
if (mid === -1) {
return lowest_diff;
}
// var sum1_new = sum1 - A[mid];
// var sum2_new = sum2 + A[mid];
// sum_diff_new = sum1_new - sum2_new = sum1 - sum2 - 2*A[mid] = sum_diff - 2*A[mid];
console.log("sum_diff:", sum_diff);
sum_diff_new = sum_diff + 2*A[mid];
console.log("sum_diff_new:", sum_diff_new);
diff_negative = (sum_diff_new < 0);
if (diff_negative = true) {
crossed_over = true;
var sum_diff_new_pos = Math.abs(sum_diff_new);
if (lowest_diff <= sum_diff_new_pos) {
return lowest_diff;
} else {
return sum_diff_new_pos;
}
}
}
}
// Issues: doesn't work e.g. with [-2, -1, -3] and [-2, -7, -20, -30, -1]

Categories

Resources