6
\$\begingroup\$

I have this Python project to draw tessellated triangles on the screen. It takes the height to form the size of the triangle, cols to specify how many triangles on a line, and rows which is how many lines it outputs. This program has taken me some time to put together and I'm not sure if this is the best way to do this. The first line is just the normal triangle, the second line and every other line after however only should show half of the triangle on the first and last column.

Here's my .py file:

import sys

print("make sure shell in full screen so the triangles are viewed properly... or disable word wrap :)")
height          = int(input("height"))
cols            = int(input("cols"))
rows            = int(input("rows"))

MAX_PAGE_WIDTH = 19 * 8

width = height * 2 - 1
if((width * cols) > MAX_PAGE_WIDTH):
    print("Triangles not gonna fit on page, please enter smaller params... :/")
    sys.exit()

def get_even_number(num):
    return num % 2 == 0

triangle        = ""

for column_row in range(rows):
    half_space      = height - 1
    stars           = 1

    for row_of_triangle in range(height):
        if(get_even_number(column_row)):
            for column in range(cols):
                triangle += " " * (half_space + 1) + "*" * stars + " " * half_space
        else:
            for column in range(cols + 1):
                if(not(column == 0)):
                    triangle += " " * (half_space + 1)

                if(column == 0 or column >= cols):
                    sub = row_of_triangle
                    if(column >= cols):
                        sub += 1
                    triangle += "*" * (stars - sub)
                else:
                    triangle += "*" * stars

                if(column < cols):
                    triangle += " " * half_space

        half_space      -= 1
        stars           += 2
        triangle        += "\n"



print(triangle)

Here is some sample output:

input: height: 6 cols: 6 rows: 4
      *           *           *           *           *           *
     ***         ***         ***         ***         ***         ***
    *****       *****       *****       *****       *****       *****
   *******     *******     *******     *******     *******     *******
  *********   *********   *********   *********   *********   *********
 *********** *********** *********** *********** *********** ***********
*           *           *           *           *           *     
**         ***         ***         ***         ***         ***         *
***       *****       *****       *****       *****       *****       **
****     *******     *******     *******     *******     *******     ***
*****   *********   *********   *********   *********   *********   ****
****** *********** *********** *********** *********** *********** *****
      *           *           *           *           *           *
     ***         ***         ***         ***         ***         ***
    *****       *****       *****       *****       *****       *****
   *******     *******     *******     *******     *******     *******
  *********   *********   *********   *********   *********   *********
 *********** *********** *********** *********** *********** ***********
*           *           *           *           *           *     
**         ***         ***         ***         ***         ***         *
***       *****       *****       *****       *****       *****       **
****     *******     *******     *******     *******     *******     ***
*****   *********   *********   *********   *********   *********   ****
****** *********** *********** *********** *********** *********** *****
\$\endgroup\$
  • 1
    \$\begingroup\$ I have rolled back Rev 5 → 4. Please see What to do when someone answers. (sys.exit() was mentioned in an answer, so you shouldn't change the question.) \$\endgroup\$ – 200_success Oct 27 '16 at 20:10
3
\$\begingroup\$

I decided to approach the problem bottom-up.

I see the problem as deciding how many asterisks and spaces should go on each line and in which order.

This action of alternateing spaces and asterisks looks like a fundamental action "building block" of this program so it should be written in its own function.

def alternate(a, b, start, start_length, a_length, b_length, times):
    """
    >>> alternate("A", "B", "C", 3, 2, 4, 3)
    "CCCBBBBAABBBB"
    """
    return start * start_length + ''.join(thing * length
        for thing, length in islice(cycle( ((b,b_length), (a,a_length))), times))

Using this function is now easy, we just need to understand how asterisks, spaces, and starting spaces change. We can deduce that simply by doing simple cases by hand and counting, or if we are lucky by looking at the output required or given by an equivalent program:

def make_triangles(height, cols, rows):
    """
    >>>     print(make_triangles(6, 6, 4))
          *           *           *           
         ***         ***         ***         
        *****       *****       *****       
       *******     *******     *******     
      *********   *********   *********   
     *********** *********** ***********
    """
    return '\n'.join(alternate(' ', '*', ' ', start_space_length, space_length, asterisk_length, cols)
            for (start_space_length, asterisk_length, space_length)
             in zip(range(height, 0, -1), range(1, height*2, 2), range(height*2-1, 0, -2)))

Using this code to make the odd lines is also easy.

Concatenate a required number of even and odd lines to build the complete output.

I suggest this method because it is logical and conceptually readable from looking at the code. Your code had myself lost over all the top-level variables and conditions leaving me without understanding how each step was correlated to the overall algorithm.

Also the alternate function is now written and documented by a test, and it may prove useful in other similar ASCII art challenges allowing code re-use.

\$\endgroup\$
5
\$\begingroup\$

Using the * operator to repeat strings is smart, but there are still a lot of nested ifs and and fors. In Python, excessive looping can often be remedied using list comprehensions and itertools.

It would definitely be a good idea to split the work into functions. The input validation code is unrelated to the output routine. The output routine, in turn, can be decomposed into smaller reusable units. In particular, it is helpful to observe that the odd rows are like the even rows, but upside-down and colour-inverted.

from itertools import chain, cycle, islice

def tesselated_triangles(height, cols, rows):
    def triangle_row(height, cols, space=' ', fill='*'):
        return [
            (
                space * (height - i) +
                fill * (2 * i + 1) +
                space * (height - i - 1)
            ) * cols
            for i in range(height)
        ]

    def alt_triangle_row(height, cols, space=' ', fill='*'):
        return reversed(triangle_row(height, cols, space=fill, fill=space))

    return '\n'.join(chain.from_iterable(
        row(height, cols)
        for row in islice(cycle([triangle_row, alt_triangle_row]), rows)
    ))

print(tesselated_triangles(6, 6, 4))

Additional remarks:

  • Since strings in Python are immutable, repeated string concatenation should be avoided. Appending just a single character using += involves allocating and copying the entire string. For this application, you should use '\n'.join(…). (Doing '\n'.join(…) would also avoid printing two newlines at the end of the output, which is wrong in my opinion.)
  • If the program failed, it would be a good idea to call sys.exit() with a non-zero status code.
  • It is uncommon in Python to write if(…): using parentheses.
  • Using extra spaces to align equals signs is discouraged in PEP 8, the official Python style guide. In this case, I really think you have overdone it.

    half_space      -= 1
    stars           += 2
    triangle        += "\n"
    
  • get_even_number() is poorly named. You're not retrieving anything; you're testing something. An appropriate name would be is_even_number() or simply is_even(). You could just eliminate the function, though, since it's so simple.
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.