Highest perimeter polyomino

14

0

This is code golf. The winner is the valid code with the smallest number of bytes.


Challenge

Given inputs M and N, the width and height of a rectangular grid of squares, output a polygon that satisfies the following:

  • The polygon edges are made up only of square edges: there are no diagonal edges - all are vertical or horizontal.
  • The polygon has no holes: every square outside the polygon may be reached by orthogonal steps on squares outside the polygon, starting from a square outside the polygon on the outer boundary of the rectangle.
  • The polygon has no self-intersection: of the square edges meeting at a vertex, no more than 2 may be part of the polygon perimeter.
  • The polygon is connected: any square in the polygon must be reachable from any other square in the polygon via orthogonal steps that stay within the polygon.
  • The polygon has the maximum possible perimeter: according to the formula shown below.

Your code must work for M and N from 1 to 255.


Formula for maximum perimeter

The challenge here is finding the most golfable of those polygons with the maximum perimeter. The maximum perimeter itself is always defined by the formula:

This is true because for a maximum perimeter every square vertex must be on the perimeter. For an odd number of vertices this is not possible and the best that can be attained is one vertex less (since the perimeter is always even).


Output

Output the shape as a string of newline separated characters (N rows of exactly M characters). Here I am using space for squares outside the polygon, and '#' for squares inside the polygon, but you may use any two visually distinct characters, provided their meaning is consistent for all inputs.

You may include up to one leading newline and up to one trailing newline.

If you wish, you may instead output M rows of exactly N characters, and you may choose M by N output for some inputs and N by M output for others.


Examples

Invalid due to a hole:

###
# #
###

Invalid due to intersection (touching diagonally - a vertex with 4 square edges on the perimeter) and, incidentally, a hole:

##
# #
###

Invalid due to being disconnected:

#
# #
  #

Valid polygon of maximum perimeter:

# #
# #
###

Credits

I initially underestimated how quickly the value of the maximum perimeter could be calculated, and was going to just ask for that value as the output. Thanks to the wonderfully helpful people in chat for explaining how to work out the maximum perimeter for arbitrary N and M and helping turn this into a challenge that will last for more than one answer...

Specifically thanks to:

Sparr, Zgarb, feersum, jimmy23013.

trichoplax

Posted 2015-06-25T17:30:09.607

Reputation: 10 499

I could name this question using polyominos or polygons (since both apply). Does anyone have a preference? You can indicate with comment voting on the following: – trichoplax – 2015-06-25T17:50:53.270

5Highest perimeter polyomino – trichoplax – 2015-06-25T17:51:13.210

1Highest perimeter connected polygon – trichoplax – 2015-06-25T17:51:22.000

N rows of exactly M characters: can we interchange the two input values if we find it convenient for certain inputs? – Level River St – 2015-06-25T18:11:58.173

@steveverrill Both inputs are from 1 to 255. I can't see a benefit to switching their order. Do you mean outputting in a different order, or something else I'm missing? – trichoplax – 2015-06-25T18:16:08.707

(even if there's a benefit I'm inclined to insist on the output being aligned as specified, but I'll delay making the decision until I'm sure I haven't misunderstood something) – trichoplax – 2015-06-25T18:17:37.323

For example, if you have a particular strategy for shapes with one side even and one side odd, you might prefer to draw an odd number of lines with an even number of characters. That would be great if M was even and N was odd. But if the user decides to give the odd number first then the even number, can we switch them round? – Level River St – 2015-06-25T18:22:16.270

@steveverrill that makes perfect sense now - thank you. I'll leave it as is for now while I think about it, as it's easier to make that change forwards than backwards, and I'll let you know what I decide after a think. – trichoplax – 2015-06-25T18:26:50.750

3@steveverrill I've edited the Output section. Does that fit your request? – trichoplax – 2015-06-25T18:38:19.060

That suits me fine! First upvote on your last comment is not mine, so I guess it suits someone else, too. – Level River St – 2015-06-25T19:12:12.903

That change in the output rules should make it simpler. Of course it was your choice if you wanted to make it easier. BTW, I believe I know how to solve this cleanly. So in the likely case that somebody else posts the same kind of solution first before I get to work on it tonight: I did not steal their idea! :) – Reto Koradi – 2015-06-25T19:49:20.717

@RetoKoradi yes I'm happy making it slightly easier in order to see a different approach – trichoplax – 2015-06-25T19:56:22.667

@RetoKoradi I only see it as making the output easier, which isn't the point of the challenge, so I'm happy with it – trichoplax – 2015-06-25T19:57:28.337

I think this will be all about generating the output with the shortest possible code. The pattern that needs to be generated looks fairly straightforward. As I expected, @steverrill already implemented exactly the pattern I also had in mind. – Reto Koradi – 2015-06-25T20:55:15.297

@RetoKoradi yes that's what I expected. Unless some languages find different patterns more suitable for golfing most people will probably choose similar patterns. – trichoplax – 2015-06-25T20:57:45.687

Answers

4

CJam, 47 bytes

l~_2%{\}|_'#:H*@({N+1$(2md\HS+*H+\SH+R=*++}fR\;

Try it online

Explanation:

l~      Get and convert input.
_2%     Calculate second value modulo 2.
{\}|    If value is even, swap the two inputs. This puts odd on top if one is odd.
_'#:H*  Create top row of all # signs. Also save away # character as shortcut for later.
@(      Pull number of rows to top, and decrement because first is done.
{       Start loop over rows.
N+      Add newline.
1$      Copy row length to top of stack.
(2md    Decrement, and calculate mod/div with 2.
\       Swap mod and div, will use div first.
HS+     "# "
*       Repeat it based on div 2 of row length.
H+      Add one more #.
\       Swap mod of earlier division to top.
SH+     " #"
R=      Pick space or # depending on even/odd row number.
*       Repeat 0 or 1 times depending on mod 2 of row length.
+       Add the possible extra character to line.
+       Add line to result.
}fR     End of for loop over lines.
\;      Remove row length from stack, leaving only result string.

There are two main cases for the result. If at least one of the sizes is odd, the pattern is a plain "rake". For example, for input 7 6:

#######
# # # #
# # # #
# # # #
# # # #
# # # #

If both sizes are even, there is an extra column where every second square is "on". For example, for input 8 6:

########
# # # # 
# # # ##
# # # # 
# # # ##
# # # # 

Now, to show that these patterns reach the theoretical maximum of the perimeter as given in the problem description, we need to confirm that the first pattern has perimeter (M + 1) * (N + 1), and the second one the same value minus 1.

For the first pattern, we have for the perimeter, with M an odd dimension:

  1. M for the top edge.
  2. 2 on the side of the top row.
  3. (M - 1) / 2 for the gaps between the teeth.
  4. (M + 1) / 2 teeth with perimeter 2 * (N - 1) + 1 each.

This adds up to:

M + 2 + (M - 1) / 2 + (M + 1) / 2 * (2 * (N - 1) + 1) =
M + 2 + (M - 1) / 2 + (M + 1) * (N - 1) + (M + 1) / 2 =
2 * M + 2 + (M + 1) * (N - 1) =
(M + 1) * 2 + (M + 1) * (N - 1) =
(M + 1) * (N + 1)

For the second case where both M and N are even, the perimeter adds up from:

  1. M for the top edge.
  2. 2 on the side of the top row.
  3. M / 2 for the open # in the top row.
  4. M / 2 teeth with perimeter 2 * (N - 1) + 1 each for the plain teeth.
  5. The rightmost tooth has an extra 2 * (N / 2 - 1) perimeter pieces for the jaggies.

Adding this all together:

M + 2 + M / 2 + (M / 2) * (2 * (N - 1) + 1) + 2 * (N / 2 - 1) =
M + 2 + (M / 2) * (2 * (N - 1) + 2) + N - 2 =
M + M * N + N =
(M + 1) * (N + 1) - 1

Reto Koradi

Posted 2015-06-25T17:30:09.607

Reputation: 4 870

I think I can save a couple of bytes by placing the jagged part on the left. Should require some less stack shuffling. But it's time to sleep... – Reto Koradi – 2015-06-26T09:04:33.503

5

Ruby, Rev 1, 66

->(m,n){n.times{|i|puts ("#"*m**(1-i%2)).rjust(m,i>n-2?"# ":" ")}}

Used raising m to power 0 o 1 to decide whether 1 or m #'s will be printed.

Used > to test for last row instead of ==.

Can't get rid of the space after puts, nor any brackets!

Ruby, Rev 0, 69

->(m,n){n.times{|i|puts ("#"*(i%2==0?m:1)).rjust(m,i==n-1?"# ":" ")}}

This is an anonymous lambda function. Use it like this:

f=->(m,n){n.times{|i|puts ("#"*(i%2==0?m:1)).rjust(m,i==n-1?"# ":" ")}}

M=gets.to_i
N=gets.to_i
f.call(M,N)

In the end, after asking if M and N could be interchanged I didnt need it.


Typical outputs for N odd. If we delete the # on their own on the right hand side, clearly we will have (N+1)(M+1). Including them to join the shape removes 2 squares of horizontal perimeter and adds 2 squares of vertical perimeter, so there is no change.

Here we rely on the expression "#"*(i%2==0?m:1) to give alternating rows of M# symbols and one # symbol, and right justify to M characters.

5                        6
5                        5
#####                    ######
    #                         #
#####                    ######
    #                         #
#####                    ######

Typical outputs for N even. 5 6 clearly has the same perimeter as 6 5, or an increment of M+1=6 compared with 5 5 by addition of vertical perimeter due to the crenelation of the bottom row. 6 6 has the same as 6 5 plus an increment of (M+1)-1=6 in the vertical perimeter. Thus they are in accordance with the formula.

5                        6
6                        6
#####                    ######
    #                         #
#####                    ######
    #                         #
#####                    ######
# # #                    # # ##

It's very handy that Ruby's rjust allows you to specify the padding to use for the empty cells. Normally the padding is set to " " but for the last row we switch to "# " (note that padding will only be needed on the last row if N is even. Where N is odd the last row will be complete and there will be no justifying, so you won't see the crenelations.)

Check it out here.

Level River St

Posted 2015-06-25T17:30:09.607

Reputation: 22 049

@Vioz- Thanks for the ideone! I tested the program down to low values of N and M to see if there were any edge cases, but I didnt bother checking if it would work for values that high. Apparently both crenellation and crenelation are correct, so I'll leave it. Will be coming back later to see if I can delete some brackets and whitespace. – Level River St – 2015-06-25T20:42:20.683

No problem for the link? I figured it would be helpful for others since I used it to test :P In regards to the spelling edit, I changed it to the first result I could find, because I've never seen the word actually used. I don't know much about Ruby (nothing, infact), but you can change i%2==0 to i%2<1 to save a byte (I've made this change to the ideone link). – Kade – 2015-06-25T20:50:50.320

Do you really need the # padding for the even last row? For example, in the very last figure, isn't the perimeter the same without the # in the bottom right corner? – Reto Koradi – 2015-06-25T21:34:20.407

@RetoKoradi it would indeed be the same perimeter - it looks like the code includes the extra # simply because it is already the way every line is ended, so it's fewer bytes than putting a space there. (I don't know ruby though...). – trichoplax – 2015-06-25T21:40:43.310

1@trichoplax your intuition is correct. The padding is "# " not " #" because the latter would give 2 adjacent # for odd M which is definitely not wanted. 2 adjacent # for even M does no harm, so I went with that. I haven't tried ljust, it may be possible to do it more cleanly with that, but it wouldn't be so obvious that I'm printing exactly M characters per row. – Level River St – 2015-06-25T21:53:13.730

@trichoplax I'm fairly new to Ruby, but I believe i%2?1:m will not work because zero is truthy in Ruby. Only false and nil (absence of data) are falsy. Will try it anyway, heh heh! – Level River St – 2015-06-25T21:58:43.820

5

C, 109 97 bytes and correctness proof

I was writting up my solution but @steveverrill beat me to it. I thought i'd share it all the same, since I included a correctness proof for the strategy used.

Reduced Code:

m,n,x;main(){for(scanf("%i%i",&m,&n); n;)putchar(x<m?"# "[x%2*(++x^m||~n&1)&&n^1]:(x=0,n--,10));}

Before Reduction:

m,n,x;

main(){
    for(scanf("%i%i",&m,&n); n;) 

        /* If x == m, prints out a newline, and iterates outer 
         * loop (x=0,n--) using comma operator.
         * Otherwise, paints a '#' on :
         *     Every even column (when x%2 is 0)
         *     On odd columns of the last row (++x^m||~n&1 is 0)
         *     On the first row (when n^1 is 0)
         * And a ' ' on anything else (when predicate is 1) */
        putchar(x<m?"# "[x%2*(++x^m||~n&1)&&n^1]:(x=0,n--,10));
}

Strategy and Proof:

Assuming the correctness of the maximum perimiter equation (M+1)(N+1) - ((M+1)(N+1)) mod 2, the following explains the optimal strategy used and proves its correctness by induction:

For odd M, we draw a hand-like shape with M/2 + 1 fingers, for example:

3x2
# # 
###

5x3
# # #
# # #
#####

We now prove this strategy is optimal for all odd M by induction:

Base Case: M=N=1
The single cell is filled. The solution is correct since (1 + 1)*(1 + 1) = 2*2 = 4, and a square has 4 sides.

Induction on width:
Assume that the hand-shape strategy works for (N, M-2) where M is odd, that is, its perimiter is optimal and is (N + 1)(M - 2 + 1) + ((M-1)(N+1)) mod 2. We now show that it will work for (N,M).

The process of adding a finger removes one edge from the polygon, and adds 3 + 2N. For example:

 5x3 -> 7x3
 # # # $
 # # # $
 #####$$

Combining this with our hypothesis that the previous perimeter was optimal, the new perimeter is:

(N + 1)*(M - 2 + 1) - ((M+1)*(N+1)) mod 2 - 1 + 3 + 2*N
(N + 1)*(M + 1) - ((M-1)*(N+1)) mod 2 - 2(N + 1) - 1 + 3 + 2*N
(N + 1)*(M + 1) - ((M-1)*(N+1)) mod 2

Since we are dealing with modulo 2 arithmetic,

((M-1)*(N+1)) mod 2 = ((M+1)*(N+1)) mod 2

Thus, proving that increasing the width by adding fingers leads to an optimal perimeter.

Induction on height:
Assume the hand-shape strategy works for (N-1, M), where M is odd, that is, its perimeter is optimal and is N(M + 1) + ((M+1)N) mod 2. We now show that it will work for (N,M).

Increasing the height of the hand merely elongates the fingers, located at the first and every other x-index. For each height increase, each finger adds two to the perimeter, and there are (M+1)/2 fingers, thus, an increase in N leads to an increase of 2(M+1)/2=M+1 in the perimeter.

Combining this with the hypothesis, we have that the new perimeter is:

N*(M + 1) + ((M+1)*N) mod 2 + M + 1
(N + 1)*(M + 1) + ((M+1)*N) mod 2

Modular arithmetic permits us to simplify the last term, so that we obtain:

(N + 1)*(M + 1) + ((M+1)*(N+1)) mod 2

Proving that the solution is optimal for all N>0 and odd M>0.

For even M, we fill in the board the same as we would for odd M, but we add crenelations to the last segment, for example:

4x3
# ##
# # 
####

6x4
# # #
# # ##
# # #
######

We now prove that this strategy is optimal.

Induction for even M:
Assume that the the solution is correct for (N,M-1), with odd M-1 (as was proven in the last case), which has an optimal perimeter of (N + 1)M - (M(N+1)) mod 2. We now show that it will work for (N,M).

Like increasing the fingers, each crenelation adds two to the perimeter of the polygon. The total number of crenelations is (N + N mod 2)/2, for a total of N + N mod 2 perimeter added.

Combining this with the hypothesis, we have that the new perimeter is:

(N + 1)*M - (M*(N+1)) mod 2 + N + N mod 2
(N + 1)*(M + 1) - (M*(N+1)) mod 2 + N mod 2 - 1
(N + 1)*(M + 1) - (M*(N+1)) mod 2 - (N + 1) mod 2

We have that

(M*(N+1)) mod 2 - (N + 1) mod 2 = ((M+1)*(N+1)) mod 2

Because if N is odd, then this reduces to 0=0, and if N is even, it reduces to

- A mod 2 - 1 = -(A + 1) mod 2

Thus the strategy is optimal for all M,N>0.

André Harder

Posted 2015-06-25T17:30:09.607

Reputation: 116

2That's a lot of math! Couldn't you just calculate the perimeter of the shape you are creating, and show that it matches the provided maximum value? You know how many "fingers" you have, how long each finger is, etc. So calculating the perimeter should be reasonably easy. – Reto Koradi – 2015-06-25T21:39:43.680

True. In some respects, I feel the induction path is more intuitive, since it's additive, but yes, it does lead to a more lengthy explanation. – André Harder – 2015-06-25T21:53:53.650

You might want to know the perimeter equals to the number of integer points it passes. – jimmy23013 – 2015-06-26T10:12:48.843