Let's say I have two intervals,
[a1, a2] and [b1, b2]
Where a1,a2,b1,b2 are all in the range [0, 2 pi]. Now, given these two intervals, I want to find their overlapping interval. This is quite tricky. Since an example of two intervals is
[5, 1] and [0, 6]
Which are sketched below (the red areas are the intervals).
Notice that these two intervals return an overlapping interval that consists of two intervals:
[0,1] and [5,6]
There are multiple different cases that must be treated, is there any known algorithm that does this?
I do not know of an existing algorithm (which doesn't mean there isn't one), but here's one I've come up with.
As already mentioned by #Michael Kenzel, numbers don't increase monotonically, which makes everything very complicated.
But we can observe that we can unroll the circle onto the number line.
Then each interval then appears infinitely many times with a period of 2π.
Let's first define a normalize operation as following:
normalize([a, b]):
if (a > b):
a -= 2π
Using this operation we unroll both our intervals onto a [-2π, 2π] part of the number line.
Example intervals:
[2, 5] -> [2, 5]
[4, π] -> [-2, π]
Two intervals on a circle can overlap at most 2 times.
(I don't have a proper proof of this, but intuitively: an overlap starts where one interval started and another one has not ended. This can happen only once on a number line and twice in our case.)
By just looking at normalized intervals, we can miss one of the overlaps. In the example above we would detect [2, π] overlap and miss [4, 5]. This happens because we have unrolled the original intervals not onto [0, 2π], but a twice longer part of the number line, [-2π, 2π].
To correct for that, we can, for example, take the part that falls onto the negative line and shift it by 2π to the right, this way having all pieces in the original [0, 2π]. But it is computationally ineffective, as we will, in the worst case, have to test 2 pieces on one interval against 2 pieces of another interval - total of 4 operations.
Here is an illustration of such an unlucky example that will require 4 comparisons:
If we want to be a bit more efficient, we will try to do only 2 interval-vs-interval operations. We won't need more as there will be at most 2 overlaps.
As the intervals repeat infinitely on the number line with the period of 2π, we can just take 2 neighboring duplicates of the first interval and compare them against the second one.
To make sure that the second interval will be, so to say, in between those duplicates, we can take the one that starts earlier and add 2π to its both ends. Or subtract 2π from the one that starts later.
There will be not more than two overlaps, which can be then brought back to the [0, 2π] interval by addition/subtraction of 2π.
In our original example it would look like that:
To sum it up:
given [a, b], [c, d]
[A, B] = normalize([a, b])
[C, D] = normalize([c, d])
if (A < C):
[E, F] = [A + 2π, B + 2π]
else:
[E, F] = [A - 2π, B - 2π]
I_1 = intersect([A, B], [C, D])
I_2 = intersect([E, F], [C, D])
bring I_1 and I_2 back to the [0, 2π]
I think I didn't miss any corner cases, but feel free to point to any mistake in my logic.
The first thing to note is that no new angle is created when you discover the intersection sector of two sectors 'A' and 'B'. Sector 'A' is defined by two limits, Acw and Acc, and sector 'B' is defined by two limits, Bcw and Bcc. Here 'cw' and 'cc' denote 'ClockWise' and 'CounterClockwise'.
The boundaries of the intersection sector will be made from at most two out of these four angles. The problem is entirely concerned with selecting two out of these four angles to be the limits of the intersection sector, let's call them Icw and Icc.
It is important to distinguish between "cw" and "cc" and keep them straight throughout the problem, because any pair of angles actually defines two sectors, right? This is as you have shown in your picture at the top of this answer. The issue of "angle wraparound" will arise naturally as the problem is solved.
Some Helper Functions
OK, so we have our four angles, and we have to select two of the four to be the limits of our intersection sector. In order to do this, we need an operator that determines whether an angle dAngle falls between two limits, let's call them dLimitCW and dLimitCC.
It is in this operator that the issue of "angle wraparound" arises. I did mine by constraining all angles to the range of -π to π. To determine whether dAngle falls between dLimitCW and dLimitCC, we subtract dAngle from dLimitCW and dLimitCC, and then constrain the result to fall within the [-π, π] range by adding or subtracting 2π. This is just like rotating the polar coordinate system by the angle dAngle , so that what was dAngle is now zero; what was dLimitCW is now dLimitCWRot, and what was dLimitCC is now dLimitCCRot.
The code looks like this:
bool AngleLiesBetween(double dAngle, double dLimitCW, double dLimitCC)
{
double dLimitCWRot, dLimitCCRot;
// Rotate everything so that dAngle is on zero axis.
dLimitCWRot = constrainAnglePlusMinusPi(dLimitCW - dAngle);
dLimitCCRot = constrainAnglePlusMinusPi(dLimitCC - dAngle);
if (dLimitCWRot > dLimitCCRot)
return (signbit(dLimitCWRot * dLimitCCRot));
else
return (!signbit(dLimitCWRot * dLimitCCRot));
}
where the function constrainAnglePlusMinusPi is
double constrainAnglePlusMinusPi(double x)
{
x = fmod(x + pi, 2*pi);
if (x < 0)
x += 2*pi;
return x - pi;
}
Once we have our "angle lies between" function, we use it to select which of the four angles that made up the limit angles of the two sectors make up the intersection sector.
The Nitty Gritty
To do this, we must first detect the case in which the two angular ranges do not overlap; we do this by calling our AngleLiesBetween() function four times; if it returns a "false" all four times, there is no overlap and the intersection is undefined:
if (
(AngleLiesBetween(Acw, Bcw, Bcc) == false) &&
(AngleLiesBetween(Acc, Bcw, Bcc) == false) &&
(AngleLiesBetween(Bcw, Acw, Acc) == false) &&
(AngleLiesBetween(Bcc, Acw, Acc) == false)
)
{
// Ranges have no overlap, result is undefined.
*this = CAngleRange(0.0f, 0.0f);
return;
}
Here I'm returning a CAngleRange object containing (0.0, 0.0) to indicate "no overlap," but you can do it some other way if you like, like by having your "intersection" function return a value of "false."
Once you've handled the "no overlap" case, the rest is easy. You check each of the six remaining combinations one at a time and determine which two limits are the limits of I by their outcomes:
if ((AngleLiesBetween(Acw, Bcw, Bcc) == true) && (AngleLiesBetween(Acc, Bcw, Bcc) == false)) then Icw = Acw and Icc = Bcc;
if ((AngleLiesBetween(Acw, Bcw, Bcc) == false) && (AngleLiesBetween(Acc, Bcw, Bcc) == true)) then Icw = Bcw and Icc = Acc;
if ((AngleLiesBetween(Acw, Bcw, Bcc) == true) && (AngleLiesBetween(Acc, Bcw, Bcc) == true)) then Icw = Acw and Icc = Acc;
if ((AngleLiesBetween(Bcw, Acw, Acc) == true) && (AngleLiesBetween(Bcc, Acw, Acc) == false)) then Icw = Bcw and Icc = Acc;
if ((AngleLiesBetween(Bcw, Acw, Acc) == false) && (AngleLiesBetween(Bcc, Acw, Acc) == true)) then Icw = Acw and Icc = Bcc;
and finally
if ((AngleLiesBetween(Bcw, Acw, Acc) == true) && (AngleLiesBetween(Bcc, Acw, Acc) == true)) then Icw = Bcw and Icc = Bcc.
You don't have to constrain the results to [-π, π] or [0, 2π] because you haven't changed them; each of the result angles is just one of the angles you presented as input to the function in the first place.
Of course you can optimize and streamline the code I've given in various ways that take advantage of the symmetries inherent in the problem, but I think when all is said and done you have to compare eight separate angle combinations for "between-ness" no matter how you optimize things. I like to keep things simple and straightforward in my code, in case I have made an error and have to come back and debug it in a few years when I've forgotten all my clever optimizations and streamlining efforts.
About Angle Wraparound
Notice that the issue of "angle wraparound" got handled in function AngleLiesBetween(); we rotated the coordinate system to put the angle we are checking (which we called dAngle) for "between-ness" at zero degrees. This naturally puts the two angle limits (dLimitCW and dLimitCC) on either side of the polar origin in the case in which dAngle is between those limits. Thus, the wrap-around issue disappears; we've "rotated it out of the way," so to speak.
About the Polar Coordinate System
It may be worth noting that in the polar coordinate system I'm using, CW angles are more positive than CC angles (unless the wrap-around is between them). This is the opposite of the polar coordinates we learn in calculus class, where angles increase in the CC (counterclockwise) direction.
This is because of the quandary that results from the decision — made long, long ago — that computer devices (like display surfaces, originally implemented on cathode ray tubes) would count the vertical direction as increasing downward, instead of upward, as we learn when we study analytic geometry and calculus.
The people who made this decision did so because they wanted to display text on the screen, with the "first" line at the top, the "second" line (line 2) below the "first" line (line 1), and so forth. To make this easier to keep track of this in their early machine code, they had the +y direction go "down," as in "toward the bottom of the screen." This decision has far-reaching and annoying consequences for people who do image geometry in code.
One of the consequences is that by flipping the direction of +y, the "sense of rotation" also flipped from the conventional "angle increases counterclockwise" sense we're used to from high-school math. This issue is mostly invisible at the coding level; it only comes out when you look at your results on the screen.
This is why it is very important to write code to visualize your results on the screen before you trust them. Here "trust them" is certainly necessary before you let your customer see them.
As long as you have intervals where the numbers just increase monotonically, it's simple; you just take the max() of the minimums and the min() of the maximums and done. It would seem that the major source of complication here is the fact that you can have intervals that wrap around at 0, i.e., the numbers that are part of the interval are not monotonically-increasing. It would seem to me that one way around this problem is to simply treat intervals that wrap around as not one interval but the union of two intervals. For example, think of [5, 1] as [5, 2 pi] ∪ [0, 1]. Then the problem of finding the intersection of [5, 1] and [0, 6] turns into the problem of finding the intersection of [5, 2 pi] and [0, 6] as well as the intersection of [0, 1] and [0, 6]. Mathematically, you'd be taking advantage of the distributive law of set intersection, i.e., (A ∪ B) ∩ C = (A ∩ C) ∪ (B ∩ C). So given two intervals A and B, we would start by splitting each into two intervals, A1 and A2, and B1 and B2, where A1 and B1 each start after 0 and end before 2 pi, and A2 and B2 start before 2 pi and end before 2 pi. Slitting like this, we can compute our intersections like
(A1 ∪ A2) ∩ (B1 ∪ B2) = (A1 ∩ (B1 ∪ B2)) ∪ (A2 ∩ (B1 ∪ B2) = (A1 ∩ B1) ∪ (A1 ∩ B2) ∪ (A2 ∩ B1) ∪ (A2 ∩ B2)
i.e., compute the intersection of all combinations of A1, A2, B1, and B2…
Related
Given a set of intervals on the real line and some parameter d > 0. Find a sequence of points with gaps between neighbors less or equal to d, such that the number of intervals that contain any of the points is minimized.
To prevent trivial solutions we ask that the first point from the sequence is before the first interval, and the last point is after the last interval. The intervals can be thought of right-open.
Does this problem have a name? Maybe even an algorithm and a complexity bound?
Some background:
This is motivated by a question from topological data analysis, but it seems so general, that it could be interesting for other topics, e.g. task scheduling (given a factory that has to shut down at least once a year and wants to minimize the number of tasks inflicted by the maintenance...)
We were thinking of integer programming and minimum cuts, but the d-parameter does not quite fit. We also implemented approximate greedy solutions in n^2 and n*logn time, but they can run into very bad local optima.
Show me a picture
I draw intervals by lines. The following diagram shows 7 intervals. d is such that you have to cut at least every fourth character. At the bottom of the diagram you see two solutions (marked with x and y) to the diagram. x cuts through the four intervals in the top, whereas y cuts through the three intervals at the bottom. y is optimal.
——— ———
——— ———
———
———
———
x x x x
y y y
Show me some code:
How should we define fun in the following snippet?
intervals = [(0, 1), (0.5, 1.5), (0.5, 1.5)]
d = 1.1
fun(intervals, d)
>>> [-0.55, 0.45, 1.55] # Or something close to it
In this small example the optimal solution will cut the first interval, but not the second and third. Obviously, the algorithm should work with more complicated examples as well.
A tougher test can be the following: Given a uniform distribution of interval start times on [0, 100] and lengths uniform on [0, d], one can compute the expected number of cuts by a regular grid [0, d, 2d, 3d,..] to be slightly below 0.5*n. And the optimal solution should be better:
n = 10000
delta = 1
starts = np.random.uniform(low=0., high=99, size=n)
lengths = np.random.uniform(low=0., high=1, size=n)
rand_intervals = np.array([starts, starts + lengths]).T
regular_grid = np.arange(0, 101, 1)
optimal_grid = fun(rand_intervals)
# This computes the number of intervals being cut by one of the points
def cuts(intervals, grid):
bins = np.digitize(intervals, grid)
return sum(bins[:,0] != bins[:,1])
cuts(rand_intervals, regular_grid)
>>> 4987 # Expected to be slightly below 0.5*n
assert cuts(rand_intervals, optimal_grid) <= cuts(rand_intervals, regular_grid)
You can solve this optimally through dynamic programming by maintaining an array S[k] where S[k] is the best solution (covers the largest amount of space) while having k intervals with a point in it. Then you can repeatedly remove your lowest S[k], extend it in all possible ways (limiting yourself to the relevant endpoints of intervals plus the last point in S[k] + delta), and updating S with those new possible solutions.
When the lowest possible S[k] in your table covers the entire range, you are done.
A Python 3 solution using intervaltree from pip:
from intervaltree import Interval, IntervalTree
def optimal_points(intervals, d, epsilon=1e-9):
intervals = [Interval(lr[0], lr[1]) for lr in intervals]
tree = IntervalTree(intervals)
start = min(iv.begin for iv in intervals)
stop = max(iv.end for iv in intervals)
# The best partial solution with k intervals containing a point.
# We also store the intervals that these points are contained in as a set.
sols = {0: ([start], set())}
while True:
lowest_k = min(sols.keys())
s, contained = sols.pop(lowest_k)
# print(lowest_k, s[-1]) # For tracking progress in slow instances.
if s[-1] >= stop:
return s
relevant_intervals = tree[s[-1]:s[-1] + d]
relevant_points = [iv.begin - epsilon for iv in relevant_intervals]
relevant_points += [iv.end + epsilon for iv in relevant_intervals]
extensions = {s[-1] + d} | {p for p in relevant_points if s[-1] < p < s[-1] + d}
for ext in sorted(extensions, reverse=True):
new_s = s + [ext]
new_contained = set(tree[ext]) | contained
new_k = len(new_contained)
if new_k not in sols or new_s[-1] > sols[new_k][0][-1]:
sols[new_k] = (new_s, new_contained)
If the range and precision could be feasible for iterating over, we could first merge and count the intervals. For example,
[(0, 1), (0.5, 1.5), (0.5, 1.5)] ->
[(0, 0.5, 1), (0.5, 1, 3), (1, 1.5, 2)]
Now let f(n, k) represent the optimal solution with k points up to n on the number line. Then:
f(n, k) = min(
num_intervals(n) + f(n - i, k - 1)
)
num_intervals(n) is known in O(1)
from a pointer in the merged interval list.
n-i is not every precision point up to n. Rather, it's
every point not more than d back that marks a change
from one merged interval to the next as we move it
back from our current pointer in the merged-interval
list.
One issue to note is that we need to store the distance between the rightmost and previous point for any optimal f(n, k). This is to avoid joining f(n - i, k - 1) where the second to rightmost point would be less than d away from our current n, making the new middle point, n - i, superfluous and invalidating this solution. (I'm not sure I've thought this issue through enough. Perhaps someone could point out something that's amiss.)
How would we know k is high enough? Given that the optimal solution may be lower than the current k, we assume that the recurrence would prevent us from finding an instance based on the idea in the above paragraph:
0.......8
——— ———
——— ———
———
———
———
x x x x
y y y
d = 4
merged list:
[(1, 3, 2), (3, 4, 5), (4, 5, 3), (5, 6, 5), (6, 8, 2)]
f(4, 2) = (3, 0) // (intersections, previous point)
f(8, 3) = (3, 4)
There are no valid solutions for f(8, 4) since the
break point we may consider between interval change
in the merged list is before the second-to-last
point in f(8, 3).
I came across this question in one of the Job Interviews & i am unable to find the correct alogorithm of the solution so, i am posting this question here:
There is a robot who can move on a co-ordinate plane in eithr of the 2 ways:
Given that the robots current position is (x,y), The robot can move equal to the sum of x & y in either if the directon like so:
(x,y) -> (x+y, y)
(x,y) -> (x, x+y)
Now given a initial Point (x1, y1) and an destination point (x2, y2) you need to write a programme to check if the robot can ever reach the destination taking any number of moves.
Note: x1, y1 , x2 , y2 > 0
Explanation:
Suppose the robot's initial point is (2,3) and desintation is (7,5)
Result in this case is yes as the robot can take this path:
(2,3) -> (2, 2+3) => (2, 5)
(2,5) -> (2+5, 5) => (7,5)
Suppose the robot's initial point is (2,3) and desintation is (4,5)
Result in this case is No as no matter what path the robot takes it cannot reach (4,5)
A naive brute-force approach
One way would be to recursively explore every possible move until you reach the target.
Something to consider is that the robot can keep moving indefinitely (never reaching the target) so you need an end case so the function completes. Luckily the position is always increasing in the x and y axis, so when either the x-coordinate or y-coordinate is greater than the target, you can give up exploring that path.
So something like:
def can_reach_target(pos, target):
if pos == target:
return True
if pos[0] > target[0] or pos[1] > target[1]:
return False
return can_reach_target((pos[0], sum(pos)), target) or \
can_reach_target((sum(pos), pos[1]), target)
And it works:
>>> can_reach_target((2,3),(7,5))
True
>>> can_reach_target((2,3),(4,5))
False
A limitation is that this does not work for negative coordinates - not sure if this is a requirement, just let me know if it is and I will adapt the answer.
Bactracking
On the other hand, if negative co-ordinates are not allowed, then we can also approach this as Dave suggests. This is much more efficient, as the realisation is that there is one and only one way of the robot getting to each coordinate.
The method relies on being able to determine which way we stepped: either increasing the x-coordinate or the y-coordinate. We can determine which coordinate was last changed, by selecting the larger of the two. The following proof guarantees that this is the case.
The possibilities for a state change are:
1. (a, b) => (a+b, b) a x-coordinate change
and,
2. (a, b) => (a, a+b) a y-coordinate change
In case (1), the x-coordinate is now larger, since:
a > 0
a + b > b (add b to both sides)
and similarly, since b is also > 0, we can deduce that a+b is > a.
Now we can start from the target and ask: which coordinate led us here? And the answer is simple. If the x-coordinate is greater than the y-coordinate, subtract the y-coordinate from the x-coordinate, otherwise subtract the x-coordinate from the y-coordinate.
That is to say, for a coordinate, (x,y), if x > y, then we came from (x-y,y) otherwise (x,y-x).
The first code can now be adapted to:
def can_reach_target(pos, target):
if pos == target:
return True
if target[0] < pos[0] or target[1] < pos[1]:
return False
x, y = target
return can_reach_target(pos, (x-y,y) if x > y else (x,y-x))
which works as expected:
>>> can_reach_target((2,3),(7,5))
True
>>> can_reach_target((2,3),(4,5))
False
Timings
>>> timeit.timeit('brute_force((2,3),(62,3))',globals=locals(),number=10**5)
3.41243960801512
>>> timeit.timeit('backtracker((2,3),(62,3))',globals=locals(),number=10**5)
1.4046142909792252
>>> timeit.timeit('brute_force((2,3),(602,3))',globals=locals(),number=10**4)
3.518286211998202
>>> timeit.timeit('backtracker((2,3),(602,3))',globals=locals(),number=10**4)
1.4182081500184722
So you can see that the backtracker is nearly three times faster in both cases.
Go backwards. I'm assuming that the starting coordinates are positive. Say you want to know if a starting point of (a,b) is compatible with an end point of (x,y). One step back from (x,y) you were either at (x-y,y) or (x,y-x). If x > y choose the former, otherwise choose the latter.
I agree with Dave that going backwards is an efficient approach. If only positive coordinates are legal, then every coordinate has at most one valid parent. This lets you work backwards without a combinatorial explosion.
Here's a sample implementation:
def get_path(source, destination):
path = [destination]
c,d = destination
while True:
if (c,d) == source:
return list(reversed(path))
if c > d:
c -= d
else:
d -= c
path.append((c,d))
if c < source[0] or d < source[1]:
return None
print(get_path((1,1), (1,1)))
print(get_path((2,3), (7,5)))
print(get_path((2,3), (4,5)))
print(get_path((1,1), (6761, 1966)))
print(get_path((4795, 1966), (6761, 1966)))
Result:
[(1, 1)]
[(2, 3), (2, 5), (7, 5)]
None
[(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (6, 5), (11, 5), (16, 5), (21, 5), (26, 5), (31, 5), (36, 5), (41, 5), (46, 5), (46, 51), (46, 97), (143, 97), (143, 240), (383, 240), (623, 240), (863, 240), (863, 1103), (863, 1966), (2829, 1966), (4795, 1966), (6761, 1966)]
[(4795, 1966), (6761, 1966)]
Appendix: some observations I made along the way that might be useful for finding an O(1) solution:
(a,b) is reachable from (1,1) if and only if a and b are coprime.
If a and b have a common factor, then all children of (a,b) also have that common factor. Equivalently, if there is a path from (a,b) to (c,d), then there is also a path from (n*a, n*b) to (n*c, n*d), for any positive integer n.
if a and b are coprime and aren't (1,1), then there are infinitely many coprime coordinates that are unreachable from (a,b). By choosing (a,b) as a starting point, you're effectively limiting yourself to some sub-branch of the tree formed by (1,1). You can never reach any of the sibling branches of (a,b), where infinitely many coordinates reside.
A recursive funtion should work fine for that. You even got the number of possibilities.
def find_if_possible(x,y,x_obj,y_obj,max_depth):
if(max_depth < 0):
return 0
elif(x == x_obj and y == y_obj):
return 1
elif(x>x_obj or y>y_obj):
return 0
else:
return(sum(find_if_possible(x+y,y,x_obj,y_obj,max_depth-1),find_if_possible(x,y+x,x_obj,y_obj,max_depth-1))
The problem sounds like this: we get n-cubes. Each cube has a length (the edge's length) and a colour. The edges' lengths are distinct, but the culours are not, for instance: any two cubes can never have the same length, but it is possible to have the same colour. The colours are from 1 to p (p is given).
We have to build a cube-tower that has a maximum height, following these rules:
1) a cube cannot be placed upon a cube if they have the same colour;
2) a cube cannot pe placed upon a cube whose edge's length is smaller.
e.g: cube c1 has a length of 3, cube c2 has a length of 5. cube c1 can be placed on the top of c2, but cube c2 cannot be placed on the top of c1.
Alright, so the algorithm I came up with in order to solve this problem is this:
we sort the cubes by edge length, in descending order and we put them in an array;
we add the first cube in the array to the Tower;
we save the length of the last inserted cube( in this case, the first cube's length ) in variable l;
we save the colour of the last inserted cube( in this case, the first cube's colour ) in variable c;
we go through the rest of the array, inserting the first cube whose length is smaller than l and colour different than c and then we repeat 3-4-5;
Now what I'm having difficulties with is, how do I prove this greedy algorithm to be the optimal one? I guess that the proof has to somehow look like the ones here: http://www.cs.princeton.edu/~wayne/kleinberg-tardos/pdf/04GreedyAlgorithmsI-2x2.pdf
The question is:
Is there a case where picking the max-length cube is not optimal?
At each decision-node we have to decide if we pick a or b, given a>b:
Assume picking b is strictly optimal (implies max-height):
Case 1: col(a) == col(b)
b optimal => final tower: b, x0, x1, ...
but also valid by construction with equal height: a, x0, x1, ...
valid because: col(a) == col(b), (a > b) & (b > x0) => (a > x0) (transitivity)
contradiction!
Case 2 col(a) != col(b)
b optimal -> final tower: b, x0, x1, ...
but also valid construction with more height: a, b, x0, x1, ...
valid because: (a > b) & (col(a) != col(b)) => a before b
contradiction!
We assumed picking b is strictly optimal and showed contradictions. Picking b can only be equally good or worse than picking a (the max-length cube of the remaining ones).
While giving an answer to "Given four coordinates check whether it forms a square", I came across this answer, which checks for a parallelogram, then for a right angle. This works, but only if the points coming in are in a certain order. Namely, P1 and P3 must be "opposite" from each other, not adjacent.
So, the question. If the four points coming in could be in any order, how can you sort them so that they are in the "correct" order to make a quadrilateral?
The simplest thing I could come up with is something like:
for each valid permutation of points[]{ // see below for "valid"
generate line segment for:
points[0] -> points[1]
points[1] -> points[2]
points[2] -> points[3]
points[3] -> points[0]
if any line segment crosses another // use cross product
continue
return permutation
}
I know that most permutations are simple rotations(0123 == 1230), so I can keep the first point 'fixed'. Also, I think I could cut it down by only considering what points are in the 0 and 2 of each permutation spots, since the order of the other two don't matter. For example, if 0123 is a polygon, 0321 is also, since it generates the same segments.
This leaves me with only three basic permutations to check:
[0,1,2,3]
[0,1,3,2]
[0,2,1,3]
Each permutation has six segment-to-segment checks, so that's a total of 18 comparisons.
I can't think of any other way to do this, but it seems like I'm missing something. Is there a better way to do this? The answer given for the square question is good, but if I have to make an additional(up to) 18 checks to verify the points are in the correct order, it would be quicker just to use inter-corner distances.
Each permutation has six segment-to-segment checks, so that's a total of 18 comparisons.
You do not need to check all the segments: it would be sufficient to check that segments [0-2] and [1-3] (i.e. the two diagonals) intersect. You need to check that the segments intersect, not the lines to which the segments belong, i.e. an intersection outside of the segments does not count.
Once you fix the starting point "A", you end up with six possible permutations:
Two of them (A-B-D-C and A-C-D-B) are good; the remaining four are bad. You can arrive at a good one with only two checks:
Check the initial permutation; if it is good, keep it; otherwise
Swap points 1 and 2, and check the permutation; if it is good, keep it; otherwise
Revert to the original permutation, swap points 2 and 3, and keep that permutation; it is bound to be "good".
Implement a method called isParallelAndEqual(p0,p1,q0,q1). This checks if lines p1-p1 and q0-q1 are parallel and of equal length.
Given points a,b,c and d, the final result looks like:
ifParallelAndEqual(a,b,c,d)||ifParallelAndEqual(a,c,b,d)
Can't you simply check all of the below until you find one that's true? (that is, check P1 opposite each other point)
P3 = P1 + (P2-P1) + (P4-P1)
P2 = P1 + (P3-P1) + (P4-P1)
P4 = P1 + (P2-P1) + (P3-P1)
For the square variant, if they happen to be axis-aligned, you can do: (that is, the opposite point is the one which doesn't have a coordinate in common)
if (P1.x != P3.x && P1.y != P3.y)
check P3 = P1 + (P2-P1) + (P4-P1)
if (P1.x != P2.x && P1.y != P2.y)
check P2 = P1 + (P3-P1) + (P4-P1)
if (P1.x != P4.x && P1.y != P4.y)
check P4 = P1 + (P2-P1) + (P3-P1)
otherwise return false
Wouldn't it be simpler to do the following:
Check if points 0, 1 and 2 span a triangle (if they are co-linear the quadrilateral is degenerate too)
Check the following segments for intersection:
[0, 3] and [1, 2]
[1, 3] and [0, 2]
[2, 3] and [0, 1]
If none of them intersects, the quadrilateral is non-convex.
Otherwise, you should have exactly one intersecting case. You found your opposite vertices there.
I'm sure this must have been asked before, but I'm not finding it: I'm only finding related, but harder questions.
I've got four points, representing two lines like this:
A C B D
|------*---|-----+----|-*---+---|----------|
0 10 20 30 40
So in the example, AB = {7, 21} and CD = {16,26}. (The lines could be in any relation to each other, and any size.) I want to find out whether or not they overlap, and by how much if so. (In the example, the answer would be 5.) My current solution involves a bunch of complicated if/then steps, and I can't help but think there's a nice arithmetical solution. Is there?
(P.S. Really, I'm doing bounding-box intersection, but if I can get it in one dimension, the other will be the same, obviously.)
Try this:
intersects = (max(a,b) > min(c,d)) && (min(a,b) < max(c,d))
overlap = min(max(a,b), max(c,d)) - max(min(c,d), min(a,b))
If you can assume a <= b and c <= d:
intersects = (b > c) && (a < d)
overlap = min(b, d) - max(c, a)
You can also calculate intersects as follows:
intersects = (overlap > 0)
A line segment is the intersection of two opposing rays (two half-infinite lines in opposite directions). You have two line segments to intersect -- the result is the intersection of all 4 rays. So you can factor your code as three successive ray-intersections: the leftward of the left-facing rays intersected with the rightward of the right-facing rays.
(This is another way of stating the now-accepted answer.)