I have a rectangular area of dimension: n*m. I also have a smaller rectangle of dimension: x*y. What will be the minimum number of smaller rectangles required to cover all the area of the bigger rectangle?
It's not necessary to pack the smaller rectangles. They are allowed to overlap with each other, cross the borders of the bigger rectangle if required. The only requirement is that we have to use the fewest number of x*y rectangles.
Another thing is that we can rotate the smaller rectangles if required (90 degrees rotation I mean), to minimise the number.
n,m,x and y: all are natural numbers. x, y need not be factors of n,m.
I couldn't solve it in the time given, neither could I figure out an approach. I initiated by taking up different cases of n,m being divisible by x,y or not.
update
sample test cases:
n*m = 3*3, x*y = 2*2. Result should be 4
n*m = 5*6, x*y = 3*2. Result should be 5
n*m = 68*68, x*y = 9*8. Result should be 65
(UPDATE: See newer version below.)
I think (but I have no proof at this time) that irregular tilings can be discarded, and finding the optimal solution means finding the point at which to switch the direction of the tiles.
You start off with a basic grid like this:
and the optimal solution will take one of these two forms:
So for each of these points, you calculate the number of required tiles for both options:
This is a very basic implementation. The "horizontal" and "vertical" values in the results are the number of tiles in the non-rotated zone (indicated in pink in the images).
The algorithm probably checks some things twice, and could use some memoization to speed it up.
(Testing has shown that you need to run the algorithm a second time with the x and y parameter switched, and that checking both types of solution is indeed necessary.)
function rectangleCover(n, m, x, y, rotated) {
var width = Math.ceil(n / x), height = Math.ceil(m / y);
var cover = {num: width * height, rot: !!rotated, h: width, v: height, type: 1};
for (var i = 0; i <= width; i++) {
for (var j = 0; j <= height; j++) {
var rect = i * j;
var top = simpleCover(n, m - y * j, y, x);
var side = simpleCover(n - x * i, y * j, y, x);
var total = rect + side + top;
if (total < cover.num) {
cover = {num: total, rot: !!rotated, h: i, v: j, type: 1};
}
var top = simpleCover(x * i, m - y * j, y, x);
var side = simpleCover(n - x * i, m, y, x);
var total = rect + side + top;
if (total < cover.num) {
cover = {num: total, rot: !!rotated, h: i, v: j, type: 2};
}
}
}
if (!rotated && n != m && x != y) {
var c = rectangleCover(n, m, y, x, true);
if (c.num < cover.num) cover = c;
}
return cover;
function simpleCover(n, m, x, y) {
return (n > 0 && m > 0) ? Math.ceil(n / x) * Math.ceil(m / y) : 0;
}
}
document.write(JSON.stringify(rectangleCover(3, 3, 2, 2)) + "<br>");
document.write(JSON.stringify(rectangleCover(5, 6, 3, 2)) + "<br>");
document.write(JSON.stringify(rectangleCover(22, 18, 5, 3)) + "<br>");
document.write(JSON.stringify(rectangleCover(1000, 1000, 11, 17)));
This is the counter-example Evgeny Kluev provided: (68, 68, 9, 8) which returns 68 while there is a solution using just 65 rectangles, as demonstrated in this image:
Update: improved algorithm
The counter-example shows the way for a generalisation of the algorithm: work from the 4 corners, try all unique combinations of orientations, and every position of the borders a, b, c and d between the regions; if a rectangle is left uncovered in the middle, try both orientations to cover it:
Below is a simple, unoptimised implementation of this idea; it probably checks some configurations multiple times, and it takes 6.5 seconds for the 11×17/1000×1000 test, but it finds the correct solution for the counter-example and the other tests from the previous version, so the logic seems sound.
These are the five rotations and the numbering of the regions used in the code. If the large rectangle is a square, only the first 3 rotations are checked; if the small rectangles are squares, only the first rotation is checked. X[i] and Y[i] are the size of the rectangles in region i, and w[i] and h[i] are the width and height of region i expressed in number of rectangles.
function rectangleCover(n, m, x, y) {
var X = [[x,x,x,y],[x,x,y,y],[x,y,x,y],[x,y,y,x],[x,y,y,y]];
var Y = [[y,y,y,x],[y,y,x,x],[y,x,y,x],[y,x,x,y],[y,x,x,x]];
var rotations = x == y ? 1 : n == m ? 3 : 5;
var minimum = Math.ceil((n * m) / (x * y));
var cover = simpleCover(n, m, x, y);
for (var r = 0; r < rotations; r++) {
for (var w0 = 0; w0 <= Math.ceil(n / X[r][0]); w0++) {
var w1 = Math.ceil((n - w0 * X[r][0]) / X[r][1]);
if (w1 < 0) w1 = 0;
for (var h0 = 0; h0 <= Math.ceil(m / Y[r][0]); h0++) {
var h3 = Math.ceil((m - h0 * Y[r][0]) / Y[r][3]);
if (h3 < 0) h3 = 0;
for (var w2 = 0; w2 <= Math.ceil(n / X[r][2]); w2++) {
var w3 = Math.ceil((n - w2 * X[r][2]) / X[r][3]);
if (w3 < 0) w3 = 0;
for (var h2 = 0; h2 <= Math.ceil(m / Y[r][2]); h2++) {
var h1 = Math.ceil((m - h2 * Y[r][2]) / Y[r][1]);
if (h1 < 0) h1 = 0;
var total = w0 * h0 + w1 * h1 + w2 * h2 + w3 * h3;
var X4 = w3 * X[r][3] - w0 * X[r][0];
var Y4 = h0 * Y[r][0] - h1 * Y[r][1];
if (X4 * Y4 > 0) {
total += simpleCover(Math.abs(X4), Math.abs(Y4), x, y);
}
if (total == minimum) return minimum;
if (total < cover) cover = total;
}
}
}
}
}
return cover;
function simpleCover(n, m, x, y) {
return Math.min(Math.ceil(n / x) * Math.ceil(m / y),
Math.ceil(n / y) * Math.ceil(m / x));
}
}
document.write("(3, 3, 2, 2) → " + rectangleCover(3, 3, 2, 2) + "<br>");
document.write("(5, 6, 3, 2) → " + rectangleCover(5, 6, 3, 2) + "<br>");
document.write("(22, 18, 5, 3) → " + rectangleCover(22, 18, 5, 3) + "<br>");
document.write("(68, 68, 8, 9) → " + rectangleCover(68, 68, 8, 9) + "<br>");
Update: fixed calculation of center region
As #josch pointed out in the comments, the calculation of the width and height of the center region 4 is not done correctly in the above code; Sometimes its size is overestimated, which results in the total number of rectangles being overestimated. An example where this happens is (1109, 783, 170, 257) which returns 23 while there exists a solution of 22. Below is a new code version with the correct calculation of the size of region 4.
function rectangleCover(n, m, x, y) {
var X = [[x,x,x,y],[x,x,y,y],[x,y,x,y],[x,y,y,x],[x,y,y,y]];
var Y = [[y,y,y,x],[y,y,x,x],[y,x,y,x],[y,x,x,y],[y,x,x,x]];
var rotations = x == y ? 1 : n == m ? 3 : 5;
var minimum = Math.ceil((n * m) / (x * y));
var cover = simpleCover(n, m, x, y);
for (var r = 0; r < rotations; r++) {
for (var w0 = 0; w0 <= Math.ceil(n / X[r][0]); w0++) {
var w1 = Math.ceil((n - w0 * X[r][0]) / X[r][1]);
if (w1 < 0) w1 = 0;
for (var h0 = 0; h0 <= Math.ceil(m / Y[r][0]); h0++) {
var h3 = Math.ceil((m - h0 * Y[r][0]) / Y[r][3]);
if (h3 < 0) h3 = 0;
for (var w2 = 0; w2 <= Math.ceil(n / X[r][2]); w2++) {
var w3 = Math.ceil((n - w2 * X[r][2]) / X[r][3]);
if (w3 < 0) w3 = 0;
for (var h2 = 0; h2 <= Math.ceil(m / Y[r][2]); h2++) {
var h1 = Math.ceil((m - h2 * Y[r][2]) / Y[r][1]);
if (h1 < 0) h1 = 0;
var total = w0 * h0 + w1 * h1 + w2 * h2 + w3 * h3;
var X4 = n - w0 * X[r][0] - w2 * X[r][2];
var Y4 = m - h1 * Y[r][1] - h3 * Y[r][3];
if (X4 > 0 && Y4 > 0) {
total += simpleCover(X4, Y4, x, y);
} else {
X4 = n - w1 * X[r][1] - w3 * X[r][3];
Y4 = m - h0 * Y[r][0] - h2 * Y[r][2];
if (X4 > 0 && Y4 > 0) {
total += simpleCover(X4, Y4, x, y);
}
}
if (total == minimum) return minimum;
if (total < cover) cover = total;
}
}
}
}
}
return cover;
function simpleCover(n, m, x, y) {
return Math.min(Math.ceil(n / x) * Math.ceil(m / y),
Math.ceil(n / y) * Math.ceil(m / x));
}
}
document.write("(3, 3, 2, 2) → " + rectangleCover(3, 3, 2, 2) + "<br>");
document.write("(5, 6, 3, 2) → " + rectangleCover(5, 6, 3, 2) + "<br>");
document.write("(22, 18, 5, 3) → " + rectangleCover(22, 18, 5, 3) + "<br>");
document.write("(68, 68, 9, 8) → " + rectangleCover(68, 68, 9, 8) + "<br>");
document.write("(1109, 783, 170, 257) → " + rectangleCover(1109, 783, 170, 257) + "<br>");
Update: non-optimality and recursion
It is indeed possible to create input for which the algorithm does not find the optimal solution. For the example (218, 196, 7, 15) it returns 408, but there is a solution with 407 rectangles. This solution has a center region sized 22×14, which can be covered by three 7×15 rectangles; however, the simpleCover function only checks options where all rectangles have the same orientation, so it only finds a solution with 4 rectangles for the center region.
This can of course be countered by using the algorithm recursively, and calling rectangleCover again for the center region. To avoid endless recursion, you should limit the recursions depth, and use simpleCover once you've reached a certain recursion level. To avoid the code becoming unusably slow, add memoization of intermediate results (but don't use results that were calculated in a deeper recursion level for a higher recursion level).
When adding one level of recursion and memoization of intermediate results, the algorithm finds the optimal solution of 407 for the example mentioned above, but of course takes a lot more time. Again, I have no proof that adding a certain recursion depth (or even unlimited recursion) will result in the algorithm being optimal.
Related
As in the title, in MATLAB, I need the feasible region (bounds of all feasible solutions) of
x_0 + x_1 e_1 + ... + x_n e_n
and
y_0 + y_1 e_1 + ... + y_n e_n
where all unknown e_i are in the interval [-1, 1]. I would prefer the solution to not depend on non-standard 3rd party functions.
Below is my quick-and-dirty attempt, but the complexity grows O(2^n), where n is the number of e_i. Any thoughts?
x0 = 3;
x = [1; -3; 0];
y0 = -1;
y = [3; -2; 4];
% Get all permutations of noise symbol extremities
terms = size(x, 1);
xx = zeros(2^terms, 1);
yy = zeros(2^terms, 1);
for j = 1:2^terms
e = double(bitget(j - 1, 1:terms))';
e(e == 0) = -1;
xx(j) = x0 + sum(x .* e);
yy(j) = y0 + sum(y .* e);
end
k = convhull(xx, yy);
plot(xx(k), yy(k));
% First generate all possible permutations for [-1, 1] for n terms. This is similar to what you have done but uses a matlab function
all_e = de2bi(0:(2^terms-1), terms).';
all_e(all_e == 0) = -1;
% Multiply corresponding values of x and y with those of e
xx = x0 + arrayfun(#(z) sum(x .* all_e(:, z)), 1:(2^terms));
yy = x0 + arrayfun(#(z) sum(y .* all_e(:, z)), 1:(2^terms));
You can read more about the function de2bi here
A method to find the absolute minimum and maximum bounds is as follows:
max_e = double(x >= 0);
min_e = double(~max_e);
max_e(max_e == 0) = -1;
min_e(min_e == 0) = -1;
absMax = x0 + sum(x .* max_e);
absMin = x0 + sum(x .* min_e);
Similarly you could do for y
I recently encountered the following
problem:
Given a set of points with height yᵢ, find the height of the line for which the average distance to points above equals the average distance to points below the line:
More abstract definition: Given a set of real valued data points Y = {y1, ..., yn}, find ȳ which splits Y into two sets Y⁺ = {y ∊ Y : y > ȳ} and Y⁻ = {y ∊ Y : y < ȳ} so that the average distance between ȳ and elements of Y⁺ equals the average distance between ȳ and elements of Y⁻.
Naive solution: Initialize ȳ with the average of Y, compute average upper and lower distances and iteratively move up or down depending on whether the upper or lower average distance is greater.
Question: This problem is pretty basic, so there is probably a better solution (?) Even a non-iterative algebraic algorithm?
As mentioned in the comment, if you know which points are above and below the line, then you can solve it like this:
a = number of points above the line
b = number of points below the line
sa = sum of all y above the line
sb = sum of all y below the line
Now we can create the following equation:
(sa - a * y) / a = (b * y - sb) / b | * a * b
sa * b - a * b * y = a * b * y - a * sb | + a * b * y + a * sb
sa * b + a * sb = 2 * a * b * y | / (2 * a * b)
==> y = (a * sb + b * sa) / (2 * a * b)
= sa / (2 * a) + sb / (2 * b)
= (sa / a + sb / b) / 2
If we interprete the result then we could say it is the average between the averages of the points above and below the line.
An iterative solution based on maraca's answer:
Initialize ȳ with the mean of the given values.
Split the given values into those above and below ȳ.
Calculate the new optimal ȳ for this split.
Repeat until ȳ converges.
This is slightly faster than the algorithm outlined in the question.
// Find mean with equal average distance to upper and lower values:
function findEqualAverageDistanceMean(values) {
let mean = values.reduce((a, b) => a + b) / values.length,
last = NaN;
// Iteratively equalize average distances:
while (last != mean) {
let lower_total = 0,
lower_n = 0,
upper_total = 0,
upper_n = 0;
for (let value of values) {
if (value > mean) {
upper_total += value;
++upper_n;
} else if (value < mean) {
lower_total += value;
++lower_n;
}
}
last = mean;
mean = (upper_total / upper_n + lower_total / lower_n) / 2;
}
return mean;
}
// Example:
let canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
points = Array.from({length: 100}, () => Math.random() ** 4),
mean = points.reduce((a, b) => a + b) / points.length,
equalAverageDistanceMean = findEqualAverageDistanceMean(points);
function draw(points, mean, equalAverageDistanceMean) {
for (let [i, point] of points.entries()) {
ctx.fillStyle = (point < equalAverageDistanceMean) ? 'red' : 'green';
ctx.fillRect(i * canvas.width / points.length, canvas.height * point, 3, 3);
}
ctx.fillStyle = 'black';
ctx.fillRect(0, canvas.height * mean, canvas.width, .5);
ctx.fillRect(0, canvas.height * equalAverageDistanceMean, canvas.width, 3);
}
draw(points, mean, equalAverageDistanceMean);
<canvas id="canvas" width="400" height="200">
Example :
A=5, B=2, N=12
Then let x=2, y=1, so 12 - (5(2) + 2(1)) = 0.
Another example:
A=5, B=4, N=12
Here x=1, y=1 is the best possible. Note x=2, y=0 would be better except that x=0 is not allowed.
I'm looking for something fast.
Note it's sufficient to find the value of Ax+By. It's not necessary to give x or y explicitly.
If gcd(A,B)|N, then N is your maximal value. Otherwise, it's the greatest multiple of gcd(A,B) that's smaller than N. Using 4x+2y=13 as an example, that value is gcd(4,2)*6=12 realized by 4(2)+2(2)=12 (among many solutions).
As a formula, your maximal value is Floor(N/gcd(A,B))*gcd(A,B).
Edit: If both x and y must be positive, this may not work. However, won't even be a solution if A+B>N. Here's an algorithm for you...
from math import floor, ceil
def euclid_wallis(m, n):
col1 = [1, 0, m]
col2 = [0, 1, n]
while col2[-1] != 0:
f = -1 * (col1[-1] // col2[-1])
col2, col1 = [x2 * f + x1 for x1, x2 in zip(col1, col2)], col2
return col1, col2
def positive_solutions(A, B, N):
(x, y, gcf), (cx, cy, _) = euclid_wallis(A, B)
f = N // gcf
while f > 0:
fx, fy, n = f*x, f*y, f*gcf
k_min = (-fx + 0.) / cx
k_max = (-fy + 0.) / cy
if cx < 0:
k_min, k_max = k_max, k_min
if floor(k_min) + 1 <= ceil(k_max) - 1:
example_k = int(floor(k_min) + 1)
return fx + cx * example_k, fy + cy * example_k, n
if k_max <= 1:
raise Exception('No solution - A: {}, B: {}, N: {}'.format(A, B, N))
f -= 1
print positive_solutions(5, 4, 12) # (1, 1, 9)
print positive_solutions(2, 3, 6) # (1, 1, 5)
print positive_solutions(23, 37, 238) # (7, 2, 235)
A brute-force O(N^2 / A / B) algorithm, implemented in plain Python3:
import math
def axby(A, B, N):
return [A * x + B * y
for x in range(1, 1 + math.ceil(N / A))
for y in range(1, 1 + math.ceil(N / B))
if (N - A * x - B * y) >= 0]
def bestAxBy(A, B, N):
return min(axby(A, B, N), key=lambda x: N - x)
This matched your examples:
In [2]: bestAxBy(5, 2, 12)
Out[2]: 12 # 5 * (2) + 2 * (1)
In [3]: bestAxBy(5, 4, 12)
Out[3]: 9 # 5 * (1) + 4 * (1)
Have no idea what algorithm that might be, but I think you need something like that (C#)
static class Program
{
static int solve( int a, int b, int N )
{
if( a <= 0 || b <= 0 || N <= 0 )
throw new ArgumentOutOfRangeException();
if( a + b > N )
return -1; // Even x=1, y=1 still more then N
int x = 1;
int y = ( N - ( x * a ) ) / b;
int zInitial = a * x + b * y;
int zMax = zInitial;
while( true )
{
x++;
y = ( N - ( x * a ) ) / b;
if( y <= 0 )
return zMax; // With that x, no positive y possible
int z = a * x + b * y;
if( z > zMax )
zMax = z; // Nice, found better
if( z == zInitial )
return zMax; // x/y/z are periodical, returned where started, meaning no new values are expected
}
}
static void Main( string[] args )
{
int r = solve( 5, 4, 12 );
Console.WriteLine( "{0}", r );
}
}
A question last week defined the zig zag ordering on an n by m matrix and asked how to list the elements in that order.
My question is how to quickly find the ith item in the zigzag ordering? That is, without traversing the matrix (for large n and m that's much too slow).
For example with n=m=8 as in the picture and (x, y) describing (row, column)
f(0) = (0, 0)
f(1) = (0, 1)
f(2) = (1, 0)
f(3) = (2, 0)
f(4) = (1, 1)
...
f(63) = (7, 7)
Specific question: what is the ten billionth (1e10) item in the zigzag ordering of a million by million matrix?
Let's assume that the desired element is located in the upper half of the matrix. The length of the diagonals are 1, 2, 3 ..., n.
Let's find the desired diagonal. It satisfies the following property:
sum(1, 2 ..., k) >= pos but sum(1, 2, ..., k - 1) < pos. The sum of 1, 2, ..., k is k * (k + 1) / 2. So we just need to find the smallest integer k such that k * (k + 1) / 2 >= pos. We can either use a binary search or solve this quadratic inequality explicitly.
When we know the k, we just need to find the pos - (k - 1) * k / 2 element of this diagonal. We know where it starts and where we should move(up or down, depending on the parity of k), so we can find the desired cell using a simple formula.
This solution has an O(1) or an O(log n) time complexity(it depends on whether we use a binary search or solve the inequation explicitly in step 2).
If the desired element is located in the lower half of the matrix, we can solve this problem for a pos' = n * n - pos + 1 and then use symmetry to get the solution to the original problem.
I used 1-based indexing in this solution, using 0-based indexing might require adding +1 or -1 somewhere, but the idea of the solution is the same.
If the matrix is rectangular, not square, we need to consider the fact the length of diagonals look this way: 1, 2, 3, ..., m, m, m, .., m, m - 1, ..., 1(if m <= n) when we search for the k, so the sum becomes something like k * (k + 1) / 2 if k <= m and k * (k + 1) / 2 + m * (k - m) otherwise.
import math, random
def naive(n, m, ord, swap = False):
dx = 1
dy = -1
if swap:
dx, dy = dy, dx
cur = [0, 0]
for i in range(ord):
cur[0] += dy
cur[1] += dx
if cur[0] < 0 or cur[1] < 0 or cur[0] >= n or cur[1] >= m:
dx, dy = dy, dx
if cur[0] >= n:
cur[0] = n - 1
cur[1] += 2
if cur[1] >= m:
cur[1] = m - 1
cur[0] += 2
if cur[0] < 0: cur[0] = 0
if cur[1] < 0: cur[1] = 0
return cur
def fast(n, m, ord, swap = False):
if n < m:
x, y = fast(m, n, ord, not swap)
return [y, x]
alt = n * m - ord - 1
if alt < ord:
x, y = fast(n, m, alt, swap if (n + m) % 2 == 0 else not swap)
return [n - x - 1, m - y - 1]
if ord < (m * (m + 1) / 2):
diag = int((-1 + math.sqrt(1 + 8 * ord)) / 2)
parity = (diag + (0 if swap else 1)) % 2
within = ord - (diag * (diag + 1) / 2)
if parity: return [diag - within, within]
else: return [within, diag - within]
else:
ord -= (m * (m + 1) / 2)
diag = int(ord / m)
within = ord - diag * m
diag += m
parity = (diag + (0 if swap else 1)) % 2
if not parity:
within = m - within - 1
return [diag - within, within]
if __name__ == "__main__":
for i in range(1000):
n = random.randint(3, 100)
m = random.randint(3, 100)
ord = random.randint(0, n * m - 1)
swap = random.randint(0, 99) < 50
na = naive(n, m, ord, swap)
fa = fast(n, m, ord, swap)
assert na == fa, "(%d, %d, %d, %s) ==> (%s), (%s)" % (n, m, ord, swap, na, fa)
print fast(1000000, 1000000, 9999999999, False)
print fast(1000000, 1000000, 10000000000, False)
So the 10-billionth element (the one with ordinal 9999999999), and the 10-billion-first element (the one with ordinal 10^10) are:
[20331, 121089]
[20330, 121090]
An analytical solution
In the general case, your matrix will be divided in 3 areas:
an initial triangle t1
a skewed part mid where diagonals have a constant length
a final triangle t2
Let's call p the index of your diagonal run.
We want to define two functions x(p) and y(p) that give you the column and row of the pth cell.
Initial triangle
Let's look at the initial triangular part t1, where each new diagonal is one unit longer than the preceding.
Now let's call d the index of the diagonal that holds the cell, and
Sp = sum(di) for i in [0..p-1]
We have p = Sp + k, with 0 <=k <= d and
Sp = d(d+1)/2
if we solve for d, it brings
d²+d-2p = 0, a quadratic equation where we retain only the positive root:
d = (-1+sqrt(1+8*p))/2
Now we want the highest integer value closest to d, which is floor(d).
In the end, we have
p = d + k with d = floor((-1+sqrt(1+8*p))/2) and k = p - d(d+1)/2
Let's call
o(d) the function that equals 1 if d is odd and 0 otherwise, and
e(d) the function that equals 1 if d is even and 0 otherwise.
We can compute x(p) and y(p) like so:
d = floor((-1+sqrt(1+8*p))/2)
k = p - d(d+1)/2
o = d % 2
e = 1 - o
x = e*d + (o-e)*k
y = o*d + (e-o)*k
even and odd functions are used to try to salvage some clarity, but you can replace
e(p) with 1 - o(p) and have slightly more efficient but less symetric formulaes for x and y.
Middle part
let's consider the smallest matrix dimension s, i.e. s = min (m,n).
The previous formulaes hold until x or y (whichever comes first) reaches the value s.
The upper bound of p such as x(i) <= s and y(i) <= s for all i in [0..p]
(i.e. the cell indexed by p is inside the initial triangle t1) is given by
pt1 = s(s+1)/2.
For p >= pt1, diagonal length remains equal to s until we reach the second triangle t2.
when inside mid, we have:
p = s(s+1)/2 + ds + k with k in [0..s[.
which yields:
d = floor ((p - s(s+1)/2)/s)
k = p - ds
We can then use the same even/odd trick to compute x(p) and y(p):
p -= s(s+1)/2
d = floor (p / s)
k = p - d*s
o = (d+s) % 2
e = 1 - o
x = o*s + (e-o)*k
y = e*s + (o-e)*k
if (n > m)
x += d+e
y -= e
else
y += d+o
x -= o
Final triangle
Using symetry, we can calculate pt2 = m*n - s(s+1)/2
We now face nearly the same problem as for t1, except that the diagonal may run in the same direction as for t1 or in the reverse direction (if n+m is odd).
Using symetry tricks, we can compute x(p) and y(p) like so:
p = n*m -1 - p
d = floor((-1+sqrt(1+8*p))/2)
k = p - d*(d+1)/2
o = (d+m+n) % 2
e = 1 - $o;
x = n-1 - (o*d + (e-o)*k)
y = m-1 - (e*d + (o-e)*k)
Putting all together
Here is a sample c++ implementation.
I used 64 bits integers out of sheer lazyness. Most could be replaced by 32 bits values.
The computations could be made more effective by precomputing a few more coefficients.
A good part of the code could be factorized, but I doubt it is worth the effort.
Since this is just a quick and dirty proof of concept, I did not optimize it.
#include <cstdio> // printf
#include <algorithm> // min
using namespace std;
typedef long long tCoord;
void panic(const char * msg)
{
printf("PANIC: %s\n", msg);
exit(-1);
}
struct tPoint {
tCoord x, y;
tPoint(tCoord x = 0, tCoord y = 0) : x(x), y(y) {}
tPoint operator+(const tPoint & p) const { return{ x + p.x, y + p.y }; }
bool operator!=(const tPoint & p) const { return x != p.x || y != p.y; }
};
class tMatrix {
tCoord n, m; // dimensions
tCoord s; // smallest dimension
tCoord pt1, pt2; // t1 / mid / t2 limits for p
public:
tMatrix(tCoord n, tCoord m) : n(n), m(m)
{
s = min(n, m);
pt1 = (s*(s + 1)) / 2;
pt2 = n*m - pt1;
}
tPoint diagonal_cell(tCoord p)
{
tCoord x, y;
if (p < pt1) // inside t1
{
tCoord d = (tCoord)floor((-1 + sqrt(1 + 8 * p)) / 2);
tCoord k = p - (d*(d + 1)) / 2;
tCoord o = d % 2;
tCoord e = 1 - o;
x = o*d + (e - o)*k;
y = e*d + (o - e)*k;
}
else if (p < pt2) // inside mid
{
p -= pt1;
tCoord d = (tCoord)floor(p / s);
tCoord k = p - d*s;
tCoord o = (d + s) % 2;
tCoord e = 1 - o;
x = o*s + (e - o)*k;
y = e*s + (o - e)*k;
if (m > n) // vertical matrix
{
x -= o;
y += d + o;
}
else // horizontal matrix
{
x += d + e;
y -= e;
}
}
else // inside t2
{
p = n * m - 1 - p;
tCoord d = (tCoord)floor((-1 + sqrt(1 + 8 * p)) / 2);
tCoord k = p - (d*(d + 1)) / 2;
tCoord o = (d + m + n) % 2;
tCoord e = 1 - o;
x = n - 1 - (o*d + (e - o)*k);
y = m - 1 - (e*d + (o - e)*k);
}
return{ x, y };
}
void check(void)
{
tPoint move[4] = { { 1, 0 }, { -1, 1 }, { 1, -1 }, { 0, 1 } };
tPoint pos;
tCoord dir = 0;
for (tCoord p = 0; p != n * m ; p++)
{
tPoint dc = diagonal_cell(p);
if (pos != dc) panic("zot!");
pos = pos + move[dir];
if (dir == 0)
{
if (pos.y == m - 1) dir = 2;
else dir = 1;
}
else if (dir == 3)
{
if (pos.x == n - 1) dir = 1;
else dir = 2;
}
else if (dir == 1)
{
if (pos.y == m - 1) dir = 0;
else if (pos.x == 0) dir = 3;
}
else
{
if (pos.x == n - 1) dir = 3;
else if (pos.y == 0) dir = 0;
}
}
}
};
void main(void)
{
const tPoint dim[] = { { 10, 10 }, { 11, 11 }, { 10, 30 }, { 30, 10 }, { 10, 31 }, { 31, 10 }, { 11, 31 }, { 31, 11 } };
for (tPoint d : dim)
{
printf("Checking a %lldx%lld matrix...", d.x, d.y);
tMatrix(d.x, d.y).check();
printf("done\n");
}
tCoord p = 10000000000;
tMatrix matrix(1000000, 1000000);
tPoint cell = matrix.diagonal_cell(p);
printf("Coordinates of %lldth cell: (%lld,%lld)\n", p, cell.x, cell.y);
}
Results are checked against "manual" sweep of the matrix.
This "manual" sweep is a ugly hack that won't work for a one-row or one-column matrix, though diagonal_cell() does work on any matrix (the "diagonal" sweep becomes linear in that case).
The coordinates found for the 10.000.000.000th cell of a 1.000.000x1.000.000 matrix seem consistent, since the diagonal d on which the cell stands is about sqrt(2*1e10), approx. 141421, and the sum of cell coordinates is about equal to d (121090+20330 = 141420). Besides, it is also what the two other posters report.
I would say there is a good chance this lump of obfuscated code actually produces an O(1) solution to your problem.
How would I write this function? Any examples appreciated
function isPointBetweenPoints(currPoint, point1, point2):Boolean {
var currX = currPoint.x;
var currY = currPoint.y;
var p1X = point1.x;
var p1y = point1.y;
var p2X = point2.x;
var p2y = point2.y;
//here I'm stuck
}
Assuming that point1 and point2 are different, first you check whether the point lies on the line. For that you simply need a "cross-product" of vectors point1 -> currPoint and point1 -> point2.
dxc = currPoint.x - point1.x;
dyc = currPoint.y - point1.y;
dxl = point2.x - point1.x;
dyl = point2.y - point1.y;
cross = dxc * dyl - dyc * dxl;
Your point lies on the line if and only if cross is equal to zero.
if (cross != 0)
return false;
Now, as you know that the point does lie on the line, it is time to check whether it lies between the original points. This can be easily done by comparing the x coordinates, if the line is "more horizontal than vertical", or y coordinates otherwise
if (abs(dxl) >= abs(dyl))
return dxl > 0 ?
point1.x <= currPoint.x && currPoint.x <= point2.x :
point2.x <= currPoint.x && currPoint.x <= point1.x;
else
return dyl > 0 ?
point1.y <= currPoint.y && currPoint.y <= point2.y :
point2.y <= currPoint.y && currPoint.y <= point1.y;
Note that the above algorithm if entirely integral if the input data is integral, i.e. it requires no floating-point calculations for integer input. Beware of potential overflow when calculating cross though.
P.S. This algorithm is absolutely precise, meaning that it will reject points that lie very close to the line but not precisely on the line. Sometimes this is not what's needed. But that's a different story.
Distance(point1, currPoint)
+ Distance(currPoint, point2)
== Distance(point1, point2)
But be careful if you have floating point values, things are different for them...
When concerned about the computational cost of computing "the square roots", don't:
Just compare "the squares".
This is independent of Javascript. Try the following algorithm, with points p1=point1 and p2=point2, and your third point being p3=currPoint:
v1 = p2 - p1
v2 = p3 - p1
v3 = p3 - p2
if (dot(v2,v1)>0 and dot(v3,v1)<0) return between
else return not between
If you want to be sure it's on the line segment between p1 and p2 as well:
v1 = normalize(p2 - p1)
v2 = normalize(p3 - p1)
v3 = p3 - p2
if (fabs(dot(v2,v1)-1.0)<EPS and dot(v3,v1)<0) return between
else return not between
You want to check whether the slope from point1 to currPoint is the same as the slope from currPoint to point2, so:
m1 = (currY - p1Y) / (currX - p1X);
m2 = (p2Y - currY) / (p2X - currX);
You also want to check whether currPoint is inside the box created by the other two, so:
return (m1 == m2) && (p1Y <= currY && currY <= p2Y) && (p1X <= currX && currX <= p2X);
Edit: This is not a very good method; look at maxim1000's solution for a much more correct way.
I'll use Triangle approach:
First, I'll check the Area, if the Area is close to 0, then the Point lies on the Line.
But think about the case where the length of AC is so great, then the Area increases far from 0, but visually, we still see that B is on AC: that when we need to check the height of the triangle.
To do this, we need to remember the formula we learn from first grade: Area = Base * Height / 2
Here is the code:
bool Is3PointOn1Line(IList<Vector2> arrVert, int idx1, int idx2, int idx3)
{
//check if the area of the ABC triangle is 0:
float fArea = arrVert[idx1].x * (arrVert[idx2].y - arrVert[idx3].y) +
arrVert[idx2].x * (arrVert[idx3].y - arrVert[idx1].y) +
arrVert[idx3].x * (arrVert[idx1].y - arrVert[idx2].y);
fArea = Mathf.Abs(fArea);
if (fArea < SS.EPSILON)
{
//Area is zero then it's the line
return true;
}
else
{
//Check the height, in case the triangle has long base
float fBase = Vector2.Distance(arrVert[idx1], arrVert[idx3]);
float height = 2.0f * fArea / fBase;
return height < SS.EPSILON;
}
}
Usage:
Vector2[] arrVert = new Vector2[3];
arrVert[0] = //...
arrVert[1] = //...
arrVert[2] = //...
if(Is3PointOn1Line(arrVert, 0, 1, 2))
{
//Ta-da, they're on same line
}
PS: SS.EPSILON = 0.01f and I use some function of Unity (for ex: Vector2.Distance), but you got the idea.
Ready for what seems to be infinitely simpler than some of these other solutions?
You pass it three points (three objects with an x and y property). Points 1 and 2 define your line, and point 3 is the point you are testing.
function pointOnLine(pt1, pt2, pt3) {
const dx = (pt3.x - pt1.x) / (pt2.x - pt1.x);
const dy = (pt3.y - pt1.y) / (pt2.y - pt1.y);
const onLine = dx === dy
// Check on or within x and y bounds
const betweenX = 0 <= dx && dx <= 1;
const betweenY = 0 <= dy && dy <= 1;
return onLine && betweenX && betweenY;
}
console.log('pointOnLine({x: 0, y: 0}, {x: 1, y: 1}, {x: 2, y: 2})');
console.log(pointOnLine({ x: 0, y: 0 }, { x: 1, y: 1 }, { x: 2, y: 2 }));
console.log('pointOnLine({x: 0, y: 0}, {x: 1, y: 1}, {x: 0.5, y: 0.5})');
console.log(pointOnLine({ x: 0, y: 0 }, { x: 1, y: 1 }, { x: 0.5, y: 0.5 }));
Edit: Simplified further according to RBarryYoung's observation.
This approach is similar to Steve's approach, just shorter and improved to use as little memory and process power as possible. But first the mathematical idea:
Let a, b be the ends of the line, ab the difference between them and p the point to check. Then p is exactly then on the line, if
a + i * ab = p
with i being a number in the interval [0;1] representing the index on the line. We can write that as two separate equations (for 2D):
a.x + i * ab.x = p.x
a.y + i * ab.y = p.y
⇔
i = (p.x - a.x) / ab.x
i = (p.y - a.y) / ab.y
Which gives us to requirements for p to be on the line from a to b:
(p.x - a.x) / ab.x = (p.y - a.y) / ab.y
and
0 ≤ i ≤ 1
In code:
function onLine(a, b, p) {
var i1 = (p.x - a.x) / (b.x - a.x), i2 = (p.y - a.y) / (b.y - a.y);
return i1 == i2 && i1 <= 0 && i1 >= 1;
}
Technically you could even inline i2 but that makes it even harder to read.
private static boolean pointInLine(int2 lineBegin, int2 lineEnd, int2 point)
{ boolean result = false;
int2 b = int2(min(lineBegin.x, lineEnd.x), min(lineBegin.y, lineEnd.y));
int2 e = int2(max(lineBegin.x, lineEnd.x), max(lineBegin.y, lineEnd.y));
if (point.x >= b.x && point.x <= e.x &&
point.y >= b.y && point.y <= e.y)
{ int2 normal = lineEnd.sub(lineBegin).perp();
int2 d0 = lineBegin.sub(point);
if (d0.dot(normal) == 0x0)
{ result = true;
}
}
return result;
}
This works for any slope, even if lineBegin == lineEnd == point.
First, create two points b and e, which ensures b <= e. (This is two support lines that have a negative slope) Check if point lands on the box (inclusive) created by those two points.
Then, get the normal of the line, which is a vector perpendicular to it.
You can do this by negating the x and transposing, x,y --> y, -x.
Then you can simply create a vector that points to the line from the point
or to the point from the line, it doesn't matter and doesn't have to be from the center of the line. Once you do that check if the vector is perpendicular to the normal by getting the dot product of the normal and the point vector.
It also makes it a bit easier on you if you've got some sort of math lib or at least a struct with x,y components. But you can do this with scalar components aswell. But getting the dot product and the normal are very simple calculations.
Here's the dot product:
.dot(int2 v) = (x * v.x + y * v.y)
Here's the perpendicular:
.perp() = new int2(y, -x)
.sub() .add() Do what you'd expect and params are in the same order.