Number of holes in a polygon

11

1

The Problem: Count the number of holes in a connected polygon. Connectivity of the polygon is guaranteed by the condition that every triangle in the input triangulation shares at least 1 side with another triangle and that there is only one such connected set of triangles.

Input is a list L of n points in the plane and a list T of 3-tuples with entries from 0...n-1. For each item in T the tuple (t_1,t_2,t_3) represents the three vertices (from the list L) of a triangle in the triangulation. Note that this is a triangulation in the sense of 'polygon triangulation', because of this there will never be two triangles in T that overlap. An additional stipulation is that you will not have to sanitize the input, L and T do not contain any repeats.

Example 1: If L = {{0,0},{1,0},{0,1},{1,2}} and T = {{0,1,2},{1,2,3}} then the polygon specified has a hole count of 0.

Figure 1

Example 2: If L = {{0,0},{1,0},{2,0},{2,1},{2,2},{1,2},{0,2},{0,1},{.5,.5},{1.5,.5},{1.5,1.5},{.5,1.5}} and T = {{5,6,11},{5,10,11},{4,5,10},{3,8,10},{2,3,9},{2,8,9},{1,2,8},{0,1,8},{0,8,11},{0,7,11},{6,7,11},{3,4,10}} then the polygon input should result in an output of 2.

Figure 2

The task is to write the shortest program (or function) that takes L and T as input and returns the number of holes. The 'winner' will be recognized as the entry with the least character count (tentative end date 1st of June).

Sample input formatting (note the 0 indexing):

0,0
1,0
0,1
1,2
0,1,2
1,2,3    

Kaya

Posted 2013-05-19T05:45:43.297

Reputation: 710

1"Connectivity of the polygon is guaranteed by the condition that every triangle in the input triangulation shares at least 1 side with another triangle." -- no. That's not a sufficient condition. Take, for example, T=1,2,3/1,2,4/5,6,7/5,6,8. Every triangle shares an edge with another triangle, but the triangulation is disconnected – John Dvorak – 2013-05-19T06:40:09.150

May we assume the input represents a valid partial triangulation (no two triangles overlap and no triangle is present twice) and the triangulation is connected? – John Dvorak – 2013-05-19T06:43:21.070

Note that the number of holes is not the genus of the shape. Every subset of plane has a genus of 0. "The genus of a connected, orientable surface is an integer representing the maximum number of cuttings along non-intersecting closed simple curves without rendering the resultant manifold disconnected"

– John Dvorak – 2013-05-19T06:47:29.070

May we also assume the input is edge-connected in the sense it is not possible to remove a finite set of points to render the shape disconnected? (ex: T=1,2,3/1,4,5 is connected but not edge-connected) – John Dvorak – 2013-05-19T06:55:22.880

Thank you for the clarification questions Jan. As a summary: (1) Yes, there will be neither overlap nor repetition (2) Yes, you may assume input is edge-connected in the sense prescribed (3) 'genus' is not strictly applicable I agree: should I strike usage of it in favor of "number of holes"? – Kaya – 2013-05-19T07:19:50.490

Alternatively I could claim that the exterior of the polygon (out to infinity in all directions) is also considered a face in the complex and then 'genus' would be valid, no? – Kaya – 2013-05-19T07:25:29.763

Genus would still be invalid. Any closed curve separates the region in two or more regions - the genus is zero. Filling a circular (or polygonal) hole by a disk (a circular - or polygonal - region of the plane) doesn't change the genus in any way. Only if the triangles didn't lie in a plane, the genus could be non-zero. – John Dvorak – 2013-05-19T11:39:51.443

May I assume the points are not duplicated (in L) either? :-) – John Dvorak – 2013-05-19T11:41:55.690

Affirmative, this is the case. – Kaya – 2013-05-19T14:58:34.217

There's nothing wrong with self-answers in general on stackExchange, and there's nothing wrong with self-answers here (except it's kinda discouraging if the optimal solution appears right away with the question. But then it's about not posting the answer right away - but you can still declare its length and see if someone else finds it or beats it). Moreover, the comment formatting has broken your indentation. – John Dvorak – 2013-05-20T03:49:57.310

? "The 'winner' will be recognized as the entry with the least character count (tentative end date 1st of April)" – DavidC – 2013-05-20T15:01:15.140

+1 The more I look into this question, the greater my appreciation for it. I now realize that it can be solved regardless of the size of the holes. – DavidC – 2013-05-20T15:19:24.083

Can the vertex of one triangle appear mid-edge on another? – Keith Randall – 2013-05-20T15:26:55.927

@KeithRandall Technically if the input is supposed to be a valid triangulation then the presence of a vertex 'mid-edge' on would create a 4 sided face. The presence of such a face in the triangulation should not interfere with topological or visual techniques for counting holes, but if it is relevant to your solution you may assume that this will not occur in the input. – Kaya – 2013-05-20T17:16:05.267

@DavidCarraher Is this not a satisfactory 'win-condition' for code-golf? I wondered when writing this question if 'char-count' as the goal might not be unfair. – Kaya – 2013-05-20T17:19:32.517

The condition is fine. It's the date I was referring to. (April 1) – DavidC – 2013-05-20T17:45:23.733

I think my brain preformed a -- when I wanted a ++. Thanks. – Kaya – 2013-05-20T17:48:32.077

2

I'm not sure why this business about end dates has started cropping up recently. You're allowed to change the accepted answer, so there's no need to set an end date. It's reasonable to have a mental idea that you'll wait a week before selecting an answer in order not to scare people into thinking that the first answer is unbeatable, but as long as you're active on the site you can change the selected answer if someone posts a better one. Relevant meta discussions include http://meta.codegolf.stackexchange.com/q/542/194 and http://meta.codegolf.stackexchange.com/q/193/194

– Peter Taylor – 2013-05-21T09:20:26.763

Can we assume that each list in T is sorted? – Volatility – 2013-05-22T10:24:10.180

@Volatility I'm going to say no to that. While when I constructed the examples I numbered my triangles from lowest index vertex to highest, I think it would be more likely for the 'author' of a triangulation to maintain constant orientation within the triangles instead of switching to accomodate the names of the vertices. – Kaya – 2013-05-22T15:13:10.343

Answers

5

GolfScript (23 chars)

~.{2*2/~}%{$}%.&,@@+,-)

Assumes input format using GolfScript array notation and quoted (or integral) coordinates. E.g.

$ golfscript codegolf11738.gs <<<END
[["0" "0"] ["1" "0"] ["2" "0"] ["2" "1"] ["2" "2"] ["1" "2"] ["0" "2"] ["0" "1"] [".5" ".5"] ["1.5" ".5"] ["1.5" "1.5"] [".5" "1.5"]] [[5 6 11] [5 10 11] [4 5 10] [3 8 10] [2 3 9] [2 8 9] [1 2 8] [0 1 8] [0 8 11] [0 7 11] [6 7 11] [3 4 10]]
END
2

(Online equivalent)

or

$ golfscript codegolf11738.gs <<<END
[[0 0] [1 0] [0 1] [1 2]] [[0 1 2] [1 2 3]]
END
0

(Online equivalent)

Peter Taylor

Posted 2013-05-19T05:45:43.297

Reputation: 41 901

5

Python, 71

What follows is a program (not a function) that calculates the desired number.

len(set().union(*(map(frozenset,zip(t,t[1:]+t))for t in T)))-len(L+T)+1

Example usage:

>>> L = ((0,0),(1,0),(2,0),(2,1),(2,2),(1,2),(0,2),(0,1),(.5,.5),(1.5,.5),(1.5,1.5),(.5,1.5))
>>> T = ((5,6,11),(5,10,11),(4,5,10),(3,8,10),(2,3,9),(2,8,9),(1,2,8),(0,1,8),(0,8,11),(0,7,11),(6,7,11),(3,4,10))
>>> len(set().union(*(map(frozenset,zip(t,t[1:]+t))for t in T)))-len(L+T)+1
2

Reinstate Monica

Posted 2013-05-19T05:45:43.297

Reputation: 929

+1 for using the splat, using frozenset instead of sorting, zip (can't say I've ever used it before, need to acquaint myself.) – Kaya – 2013-05-21T05:21:15.103

3

APL, 36

{1+(⍴⊃∪/{{⍵[⍋⍵]}¨,/3 2⍴⍵,⍵}¨⍵)-⍴⍺,⍵}

The function takes L as its left argument and T as its right.

For example:

      L←(0 0)(1 0)(0 1)(1 2)
      T←(0 1 2)(1 2 3)
      L{1+(⍴⊃∪/{{⍵[⍋⍵]}¨,/3 2⍴⍵,⍵}¨⍵)-⍴⍺,⍵}T
0
      L←(0 0)(1 0)(2 0)(2 1)(2 2)(1 2)(0 2)(0 1)(.5 .5)(1.5 .5)(1.5 1.5)(.5 1.5)
      T←(5 6 11)(5 10 11)(4 5 10)(3 8 10)(2 3 9)(2 8 9)(1 2 8)(0 1 8)(0 8 11)(0 7 11)(6 7 11)(3 4 10)
      L{1+(⍴⊃∪/{{⍵[⍋⍵]}¨,/3 2⍴⍵,⍵}¨⍵)-⍴⍺,⍵}T
2

Explanation, going from right to left:

  • ⍴⍺,⍵ concatenates the two input vectors and returns their length (V + F)
  • Stepping inside the next block:
    • ¨⍵ applies the function on the left to every element of the right argument and returns the result
    • ⍵,⍵ returns the right argument concatenated with itself
    • 3 2⍴ shapes the vector argument into three pairs. In this case it pairs together the first and second, third and first, and second and third items of the vector.
    • ,/ joins the vector argument together
    • ⍵[⍋⍵] sorts the right argument
    • ∪/ filters out any duplicates
    • ⍴⊃ turns a nested scalar into a vector, and it returns the length of it.
    • The whole function returns the number of edges in the shape (E)
  • 1 is self-explanatory (I hope...)

The whole function then returns 1+E-(V+F), or 1-(F+V-E).

Volatility

Posted 2013-05-19T05:45:43.297

Reputation: 3 206

Pretty much exactly what my GolfScript solution does. I'm surprised it's so much longer than GolfScript. – Peter Taylor – 2013-05-22T09:06:32.017

@PeterTaylor I was surprised your GolfScript solution was so much shorter! (But then again, it is GolfScript) – Volatility – 2013-05-22T09:11:44.110

2

Mathematica, 93 (not much golfed yet)

f[l_, t_] :=  Max@MorphologicalComponents[Erosion[Graphics[
                                                        GraphicsComplex[l, Polygon[t + 1]]], 1]] - 1

(Spaces added for clarity)

Testing:

f[{{0, 0}, {1, 0}, {0, 1}, {1, 2}}, {{0, 1, 2}, {1, 2, 3}}]
(*
 -> 0
*)

{l, t} = {{{0, 0}, {1,   0}, {2,    0}, {2,     1}, {2,    2}, {1, 2}, {0, 2}, 
           {0, 1}, {.5, .5}, {1.5, .5}, {1.5, 1.5}, {.5, 1.5}}, 

           {{5, 6, 11}, {5, 10, 11}, {4, 5, 10}, {3, 8, 10}, {2, 3,  9}, 
            {2, 8,  9}, {1,  2,  8}, {0, 1,  8}, {0, 8, 11}, {0, 7, 11}, {6, 7, 11}, {3, 4, 10}}};
f[l, t]
 (*
  -> 2
 *)

Dr. belisarius

Posted 2013-05-19T05:45:43.297

Reputation: 5 345

Doesn't this rely on the triangles or holes having certain minimal size (the argument to Erosion)? – John Dvorak – 2013-05-19T18:29:34.093

@JanDvorak Perhaps I'm wrong, but I think that unless you use infinite precision arithmetic, any solution will work until you reach a certain minimal size (you'll have to decide if three points are aligned or not). It's just that in this kind of solution the problem is explicitly stated. – Dr. belisarius – 2013-05-19T19:45:11.963

if you use the topological approach, you don't have to. If there are three points collinear then you require a zero-area triangle there - otherwise you have a hole. – John Dvorak – 2013-05-19T19:48:55.840

@belisarius. Here's the answer I received from Wolfram Technical Support about the discrepancy between our results: "Hello - Thank you for your email.

I have confirmed that your code gives different results on Mac and Windows. I do not think that this is intended behavior, so I have filed a report with our developers on the issue. I will be sure to pass on any useful information that I get from our developers on this issue.

Please let me know if you have any further questions.

...Technical Support Wolfram Research, Inc." – DavidC – 2013-05-21T19:25:59.030

@DavidCarraher "Yes, I have further questions: Will you send me a check for each bug?" – Dr. belisarius – 2013-05-21T21:55:19.503

2

Mathematica 76 73 72 67 62

After much experimentation, I came to realize that the precise location of the vertices was of no concern, so I represented the problem with graphs. The essential invariants, the number of triangles, edges and vertices stayed invariant (provided line crossing was avoided).

There were two kinds of internal "triangles" in the graph: those were there was presumably a face, i.e. a "filled" triangle, and those where there were not. The number of internal faces did not have any relation to the edges or vertices. That meant that poking holes in fully "filled" graphs only reduced the number of faces. I played systematically with variations among triangles, keeping track of the faces, vertices and edges. Eventually I realized that the number of holes always was equal to 1 - #faces -#vertices + #edges. This turned out to be 1 minus the Euler characteristic (which I only had known about in the context of regular polyhedra (although the length of the edges was clearly of no importance.

The function below returns the number of holes when the vertices and triangles are input. Unlike my earlier submission, it does not rely on a scan of an image. You can think of it as 1 - Euler's characteristic, i.e. 1 - (F +V -E) whereF= #faces, V=#vertices, E=#edges. The function returns the number of holes, 1 - (F + V -E), given the actual faces (triangles) and vertices.

It can be easily shown that the removal of any triangle on the outside of the complex has no affect on the Euler characteristic, regardless of whether it shares one or 2 sides with other triangles.

Note: The lower case v will be used in place of the L from the original formulation; that is, it contains the vertices themselves (not V, the number of vertices)

f is used for the T from the original formulation; that is, it contains the triangles, represented as the ordered triple of vertex indices.

Code

z=Length;1-z@#2-z@#+z[Union@@(Sort/@{#|#2,#2|#3,#3|#}&@@@#2)]&

(Thanks to Mr. Wizard for shaving off 5 chars by eliminating the replacement rule.)


Example 1

v = {{0, 0}, {1, 0}, {0, 1}, {1, 2}}; f = {{0, 1, 2}, {1, 2, 3}};

z=Length;1-z@#2-z@#+z[Union@@(Sort/@{#|#2,#2|#3,#3|#}&@@@#2)]&[v, f]

0

Zero holes.


Example 2

v = {{0, 0}, {1, 0}, {2, 0}, {2, 1}, {2, 2}, {1, 2}, {0, 2}, {0, 1}, {.5, .5}, {1.5, .5}, {1.5, 1.5}, {.5, 1.5}}; f = {{5, 6, 11}, {5, 10, 11}, {4, 5, 10}, {3, 8, 10}, {2, 3, 9}, {2, 8, 9}, {1, 2, 8}, {0, 1, 8}, {0, 8, 11}, {0, 7, 11}, {6, 7, 11}, {3, 4, 10}};

z=Length;1-z@#2-z@#+z[Union@@(Sort/@{#|#2,#2|#3,#3|#}&@@@#2)]&[v, f]

2

Thus, 2 holes are in example 2.

DavidC

Posted 2013-05-19T05:45:43.297

Reputation: 24 524

are you basically rasterising the triangulation and dumping a graphics library on that image? Doesn't that fail if a hole is too small? – John Dvorak – 2013-05-19T19:25:37.467

1your second example returns 0 here (that's why I haven't used MorphologicalEulerNumber[]). Mma 9.01, Win XP. – Dr. belisarius – 2013-05-19T19:25:37.820

I'm using 9.0.1 also, but on a Mac. Are you're saying Mathematica returns a different answer from mine on Windows? If so, that sounds like a bug (in the Windows XP version). – DavidC – 2013-05-19T19:31:47.817

@DavidCarraher Yep: http://i.stack.imgur.com/LKcr1.png

– Dr. belisarius – 2013-05-19T19:40:07.333

@Jan Dvorak. MorphologicalEulerNumber sometimes requires an image; it refuses to accept a graphics object. In these cases, the size of the hole and the resolution are critical (see http://codegolf.stackexchange.com/questions/8706/regions-of-regular-polygons/8707#8707). But here it works directly with the Graphics object, which explicitly contains all of the vertices. I imagined (or hoped) that it would use an approach that did not depend on the image. I wish I knew how it attempted to solve the issue. Perhaps some spelunking in the source code for the function will clarify things.

– DavidC – 2013-05-19T19:48:56.530

@belisarius That's a mystery. – DavidC – 2013-05-19T19:58:51.203

@JanDvorak I proposed a solution that does not rely on scanning an image. – DavidC – 2013-05-20T17:44:30.337

David, I haven't read your entire post yet (thought I anticipate a good read!) but there doesn't seem to be any point in using UndirectedEdge. Why not: {a|b,b|c,c|a}? – Mr.Wizard – 2013-05-21T14:20:38.287

@Mr.Wizard, I wanted a single binary "operator" that would dispense with the need for list brackets. You are right about the edges being unnecessary, but since I had to use some symbol, I thought, why not use something that conveys that the pairs are edges. The drawback is that the FullForm is clumsy in SE. I couldn't find a way to past the symbol alone. – DavidC – 2013-05-21T14:48:42.607

@DavidCarraher how about: z=Length;1-z@#2-z@#+z[Union@@(Sort/@{#|#2,#2|#3,#3|#}&@@@#2)]&[v,f] – Mr.Wizard – 2013-05-21T15:05:59.530

Thanks! It now fits on one line, and no more \[UndirectedEdge] in the code. – DavidC – 2013-05-21T15:47:12.787

2

Ruby, 239 characters (227 body)

def f t
e=t.flat_map{|x|x.permutation(2).to_a}.group_by{|x|x}.select{|_,x|x.one?}.keys
n=Hash[e]
(u,n=n,n.dup;e.map{|x|a,b=*x;n[a]=n[n[a]]=n[b]})until n==u
n.values.uniq.size+e.group_by(&:last).map{|_,x|x.size}.reduce(-1){|x,y|x+y/2-1}
end

note that I'm only considering the topology. I'm not using the vertex positions in any way.

caller (expects T in Mathematica or JSON format):

input = gets.chomp
input.gsub! "{", "["
input.gsub! "}", "]"
f eval(input)

Test:

f [[0,1,2],[1,2,3]]
#=> 0
f [[5, 6, 11], [5, 10, 11], [4, 5, 10], [3, 8, 10], [2, 3, 9], [2, 8, 9], [1, 2, 8], [0, 1, 8], [0, 8, 11], [0, 7, 11], [6, 7, 11], [3, 4, 10]]
#=> 2
f [[1,2,3],[3,4,5],[5,6,1],[2,3,4],[4,5,6],[6,1,2]]
#=> 1

John Dvorak

Posted 2013-05-19T05:45:43.297

Reputation: 9 048

Yay, an euler characteristic approach. That's how I did it in python. – Kaya – 2013-05-19T19:20:43.910

2

@Kaya. (See Egg of Columbus http://en.wikipedia.org/wiki/Egg_of_Columbus) Once someone has given an Eulerian answer to your question, the likelihood that others will follow increases greatly. I can assure you it's far more challenging and gratifying to discover the approach on one's own, only afterwards making the connection to Euler's work with polyhedra.

– DavidC – 2013-05-21T13:40:37.283

1

Python, 107

I realized that taking the pairs directly was shorter than from itertools import* and typing combinations(). Yet I also noticed that my solution relied on the input triangular faces having their vertices listed in consistent order. Therefore the gains in character count are not that large.

f=lambda l,t:1-len(l+t)+len(set([tuple(sorted(m))for n in[[i[:2],i[1:],[i[0],i[2]]]for i in t]for m in n]))

Python, 115

Euler characteristic approach, the verbosity of itertools seems impossible to avoid. I wonder if it would be cheaper to use a more direct technique for making pairs of vertices.

from itertools import*
f=lambda l,t:1-len(l+t)+len(set([m for n in[list(combinations(i,2)) for i in t]for m in n]))

Usage example:

> f([[0,0],[1,0],[0,1],[1,2]],[[0,1,2],[1,2,3]])
> 0
> f([[0,0],[1,0],[2,0],[2,1],[2,2],[1,2],[0,2],[0,1],[.5,.5],[1.5,.5],[1.5,1.5],[.5,1.5]],[[5,6,11],[5,10,11],[4,5,10],[3,8,10],[2,3,9],[2,8,9],[1,2,8],[0,1,8],[0,8,11],[0,7,11],[6,7,11],[3,4,10]])
> 2

Kaya

Posted 2013-05-19T05:45:43.297

Reputation: 710