Find the area of the largest convex polygon

29

4

Given a list of integer coordinates, find the area of the biggest convex polygon you can construct from the list such that -

  • every vertex is in the list
  • no element of the list is contained within the polygon.

Example:

(0, 0) (8, 0) (0, 1) (3, 1) (7, 1) (1, 2) (5, 2) (9, 2) (2, 3) (5, 3) (7, 3) (3, 4) (5, 5) (11, 5)

Visualized:

o       o
o  o   o
 o   o   o
  o  o o
   o
     o     o

The biggest convex polygon you can make from this is this:

o     
o  o  
 o   o
  o  o
   o
     o

With an area of 12.


You may take the list of coordinates in any reasonable format, and should output (in an appropriate way for your language of choice) the area of the largest convex polygon, rounded to no less than 2 digits after the decimal point.

Additionally, you must employ some sort of algorithm, and not simply brute force all subsets of the points. To enforce this, your program must solve a list of 50 vertices in under a minute on a modern PC.

Shortest code in bytes wins.

orlp

Posted 2015-08-12T21:19:03.747

Reputation: 37 067

Do you know of a fast algorithm for the worst case? – xnor – 2015-08-12T21:28:12.783

3If you want to enforce a time limit on 100 vertices, you should probably include at least one such test case (ideally several, e.g. one where all 100 vertices are part of the solution, one where 99 are, and one where only 10 are). – Martin Ender – 2015-08-12T21:35:16.480

@MartinBüttner Sadly, I can not generate this test case as I do not have a working implementation myself. The problem is rather tricky :) – orlp – 2015-08-12T21:45:07.717

@xnor A couple examples can be found here.

– orlp – 2015-08-12T21:48:04.033

"rounded to no less than 2 digits after the decimal point"? – DavidC – 2015-08-16T21:49:29.470

@DavidCarraher Your answer must be precise to 2 digits after the decimal point. So if the answer is 12 you may output 12, 12.00, 12.001 but not 12.01. – orlp – 2015-08-16T21:59:48.440

I think it'd also be interesting to see a version of this question, but instead the program must take an integer N and find the largest convex polygon contained in the image generated from primes up to N with a fixed width. – mbomb007 – 2015-08-21T20:28:16.733

This is the kind of problem that should be posted on ProjectEuler. – ricochet1k – 2015-08-21T21:49:52.693

You should have additional test cases. In your test case, the vertices of the largest convex hull form a convex chain, namely, {{5, 2}, {3, 1}, {0, 0}, {0, 1}, {1, 2}, {2, 3}, {3, 4}, {5, 5}, {5, 3}}, on the "shortest tour" of the set. Solutions do not always fall on the shortest tour, as I discovered after some effort. – DavidC – 2015-08-25T13:46:26.023

@DavidCarraher I'm not exactly certain what you mean, but you're free to add test cases yourself. – orlp – 2015-08-25T14:19:17.047

It would have been better for me to say the "largest empty convex hull", that is, the "largest convex hole". – DavidC – 2015-08-25T15:24:13.657

Answers

12

Javascript ES6, 738 bytes

((V,C,L,r,k,n,A,G,F,e,i,j,q)=>p=>{p=p.map((p,i)=>({i:i,x:p[0],y:p[1]}));A=(f,p,a,b,v,i)=>{for(i=p[n],v=V(a,b);i--;)if(f(v,V(a,p[i])))return 1};G=(p,i,a)=>{for(i=p[n]-1,a=C(p[i],p[0]);i--;)a+=C(p[i],p[i+1]);if((a/=2)>r)r=a};F=(p,s,l,f,a,b,v)=>(l=s[n],f=s[0],a=s[l-2],b=s[l-1],e[a.i][b.i]||A((a,b)=>C(a,b)?0:a.x<0==b.x<0&&a.y<0==b.y<0&&L(a)>L(b),p,a,b)?0:(p=(v=V(a,b),p[k](x=>C(v,V(a,x))>=0)),A((a,b)=>C(a,b)>0,p,b,f)?0:(p.map(q=>F(p[k](r=>q!==r),[...s,q])),s[2]&&!p[n]&&!e[b.i][f.i]?G(s):0)));e=p.map(x=>p.map(y=>x===y));for(i=p[n];i--;){for(j=i;j--;){q=p[k]((p,x)=>x-i&&x-j);F(q,[p[i],p[j]]);F(q,[p[j],p[i]]);e[i][j]=e[j][i]=1}}console.log(r)})((a,b)=>({x:b.x-a.x,y:b.y-a.y}),(a,b)=>a.x*b.y-a.y*b.x,v=>v.x*v.x+v.y*v.y,0,'filter','length')

Here's an ES5 or less version that should work in most browsers and node without tweaking: 827 bytes

eval("(%V,C,L,r,k,n,A,G,F,e,i,j,q){@%p){p=p.map(%p,i){@{i:i,x:p[0],y:p[1]}});A=%f,p,a,b,v,i){for(i=p[n],v=V(a,b);i--;)if(f(v,V(a,p[i])))@1};G=%p,i,a){for(i=p[n]-1,a=C(p[i],p[0]);i--;)a+=C(p[i],p[i+1]);if((a/=2)>r)r=a};F=%p,s,l,f,a,b,v){@(l=s[n],f=s[0],a=s[l-2],b=s[l-1],e[a.i][b.i]||A(%a,b){@C(a,b)!=0?0:a.x<0==b.x<0&&a.y<0==b.y<0&&L(a)>L(b)},p,a,b)?0:(p=(v=V(a,b),p[k](%x){@C(v,V(a,x))>=0})),A(%a,b){@C(a,b)>0},p,b,f)?0:(p.forEach(%q){@F(p[k](%r){@q!==r}),s.concat([q]))}),s[2]&&p[n]==0&&!e[b.i][f.i]?G(s):0)))};e=p.map(%x,i){@p.map(%y,j){@i==j})});for(i=p[n];i--;){for(j=i;j--;){q=p[k](%p,x){@x!=i&&x!=j});F(q,[p[i],p[j]]);F(q,[p[j],p[i]]);e[i][j]=e[j][i]=1}}console.log(r)}})(%a,b){@{x:b.x-a.x,y:b.y-a.y}},%a,b){@a.x*b.y-a.y*b.x},%v){@v.x*v.x+v.y*v.y},0,'filter','length')".replace(/%/g,'function(').replace(/@/g,'return '))

Code returns an anonymous function. As a parameter, it takes an array of points, like [[0,1],[2,3],[4,5]]. To use it you can place var f= before it, or if you want to use it from the command line, add (process.argv[2].replace(/ /g,'').slice(1,-1).split(')(').map((x)=>x.split(','))) to the end, and call it like node convpol.js '(1,2)(3,4)(5,6)'

Thanks for the challenge! As there is no reference implementation, I can't prove this is correct, but it is consistent at least for permutations of the point list. I almost didn't think this was going to work, as versions with debugging code, even disabled, were way too slow with exponential time increase. I decided to golf it anyway, and was pleased to see that it dropped down to under 2 seconds for 50 points on my machine. It can calculate approximately 130 points in 1 minute.

The algorithm is similar to the Graham scan, except that it has to search for empty convex hulls everywhere.

Explanation

Here's a high-level overview of how the algorithm works. The meat of this algorithm is just searching for counter-clockwise convex loops that don't enclose a point. The procedure is something like this:

  1. Start with a pair of points, and a list of all other points.
  2. If the current pair of points goes exactly through any point in the list, stop.
  3. Filter out all points clockwise of the current pair, since they would make the polygon concave.
  4. For all points left, do the following:
    1. If a line from this point to the first point of the chain goes through or encloses any points counter-clockwise, skip this point, since any polygons would enclose the point.
    2. Add this point to the chain, recurse from step 1 with the current chain and list of points.
  5. If there were no points left, and the chain has at least 3 points, this is a valid convex polygon. Remember the largest area of these polygons.

Also, as an optimization, we record the initial pair of the chain as checked, so any searches afterwards upon seeing this pair anywhere in the chain can immediately stop searching, since the largest polygon with this pair has already been found.

This algorithm should never find a polygon twice, and I've experimentally verified this.

ricochet1k

Posted 2015-08-12T21:19:03.747

Reputation: 516

How many cases did you test it on? And of what sizes? – DavidC – 2015-08-25T13:50:08.907

I made a random point generator, which I've used to test up to 50x50 and 70 points, which runs in under 5 seconds. I'm not sure how to verify that it always finds the largest polygon, but it has in every test I've examined. – ricochet1k – 2015-08-25T15:37:58.423

Sounds like you did a careful job. – DavidC – 2015-08-25T17:42:17.993

2+1, this is an amazing answer. You might be able to replace === and !== with == and !=, but I couldn't be sure without understanding your code... – jrich – 2015-08-25T18:19:34.897

1Thanks! Those particular === and !== are comparing objects, so nope, sadly. It used to compare indices, but (x,i)=>p.i==i (13 chars) is quite a bit longer than x=>p===x (8 chars) even after optimization. – ricochet1k – 2015-08-25T19:31:20.040

Ah well, that makes sense. Oddly that seemed to work when I tested the example. Seems you've done a thorough golfing job! – jrich – 2015-08-25T20:39:39.383

The algorithm is surprisingly sensitive to little things. While golfing, I would periodically alter the logic just enough to break some samples, but not others, particularly those with many co-linear points. Some of my logic-altering optimizations seemed to still work, but make me nervous. – ricochet1k – 2015-08-25T21:13:49.303

Would you mind describing your solution in more detail? It looks very interesting. – None – 2015-08-26T08:42:19.337

2There's an explanation for you @Lembik – ricochet1k – 2015-08-27T13:26:08.850

1You seem to have beaten the O(n^3) record mentioned in the comments of the linked SO question! – None – 2015-08-27T14:04:09.803

1Alright, I'm getting to where I don't believe it is possible that this runs in less than O(n^3). I'm very new to algorithmic complexity. – ricochet1k – 2015-08-28T07:31:57.840

After doing some perf testing, the algorithm is actually about O(n^4), it just works really fast on small numbers of points. – ricochet1k – 2015-08-29T00:54:04.720