10
\$\begingroup\$
import math

num_rows = int(input())
if num_rows % 2 != 0:
    upper_mid = math.ceil(num_rows/2)
    lower_mid = num_rows//2
    
    #prints upper normal triangle
    for row in range(0, upper_mid):                 
        for col in range(upper_mid-1, row, -1):     
            print(end=" ")                         
        for col in range(row+1):                  
            print('*', end="")
        for col in range(row):                   
            print('*', end="")
        print()
    
    # prints lower inverted triangle 
    for row in range(0, lower_mid):                
        for col in range(row+1):                    
            print(end=" ")
        for col in range(lower_mid - row):         
            print('*', end="")
        for col in range(lower_mid - row-1):    
            print('*', end="")
        print()
else:
    print("Only odd numbers!")

Input:

9

Ouput:

    *
   ***
  *****
 *******
*********
 *******
  *****
   ***
    *

This code works 100%, and prints a diamond shape star pattern. I'm a beginner in Python, and wrote this by myself, so I might have written something unnecessary and the code could be simplified, or something else. Feedback is appreciated.

\$\endgroup\$

1 Answer 1

9
\$\begingroup\$

Calling print is very slow, you should focus on building strings then have one print. Fortunately Python has some really nice string manipulation options.

for col in range(upper_mid-1, row, -1):
    print(end=" ")
  • You should rename col to _, which is standard nomenclature to say the variable is thrown away.

  • Since the print isn't based on col walking up (step=1) would be easier to understand.

    for _ in range(row, upper_mid - 1):
    
  • We can instead use * with a string and an integer to duplicate the string that amount of times.

    output = " " * (upper_mid - 1 - row)
    

Lets convert your code to remove the ranges, and a number of prints.
I'm also remove the whitespace at the end of lines, something which you should never have.

num_rows = int(input())
if num_rows % 2 != 0:
    upper_mid = math.ceil(num_rows/2)
    lower_mid = num_rows//2

    #prints upper normal triangle
    for row in range(0, upper_mid):
        print(
            " " * (upper_mid - 1 - row)
            + "*" * (row + 1)
            + "*" * row
        )

    # prints lower inverted triangle 
    for row in range(0, lower_mid):
        print(
            " " * (row + 1)
            + "*" * (lower_mid - row)
            + "*" * (lower_mid - 1 - row)
        )
else:
    print("Only odd numbers!")
  • We can see the two loops for the upper and lower triangle are very similar. If we change the lower triangle to walk from lower_mid - 1 down, then the inner parts would be the same.

  • We can also see we can merge the two lines creating the stars with row * 2 + 1

num_rows = int(input())
if num_rows % 2 != 0:
    upper_mid = math.ceil(num_rows/2)
    lower_mid = num_rows//2
    for row in range(0, upper_mid):
        print(
            " " * (upper_mid - 1 - row)
            + "*" * (2 * row + 1)
        )
    for row in reversed(range(0, lower_mid)):
        print(
            " " * (upper_mid - 1 - row)
            + "*" * (2 * row + 1)
        )
else:
    print("Only odd numbers!")

I wouldn't expect a beginner to know these. But there are some more advanced improvements we can make:

  • Making a function, to increase reusability of the code.
  • Using itertools.chain to let us not write the same body for the for loops twice.
  • Changing num_rows % 2 != 0 to a guard clause to reduce the arrow anti-pattern.
  • We know lower_mid = upper_mid - 1, so we can replace upper_mid - 1 with lower_mid and remove the need for using math.ceil.
  • Use "\n".join and a generator expression to return just a string to be printed.
  • Use typehints to document the types the function takes and returns.
import itertools


def diamond(size: int) -> str:
    if size % 2 == 0:
        raise ValueError("only odd numbers are allowed")
    mid = size // 2
    return "\n".join(
        " " * (mid - half_width) + "*" * (2 * half_width + 1)
        for half_width in itertools.chain(
            range(0, mid + 1),
            reversed(range(0, mid)),
        )
    )


print(diamond(int(input("Size of diamond: "))))
\$\endgroup\$
4
  • \$\begingroup\$ Btw "Calling print is very slow, you should focus on building strings then have one print..." can you elaborate further? Is this what you meant: """ print( " " * (upper_mid - 1 - row) + "*" * (2 * row + 1) """ instead of """ print(end='') """ and a bunch of other print functions? \$\endgroup\$
    – Anony
    Commented Oct 3, 2021 at 19:49
  • 1
    \$\begingroup\$ @Anony I don't know the actual average time of a print, but lets play a number game. Say a single call to print takes \$10^{-2}\$ seconds. If you have 10 calls then your code would take \$10^{-1}\$ seconds to complete. On the other hand joining strings using + takes \$10^{-3}\$ seconds, so if we have 10 of them the code would take \$10^{-2}\$ seconds, and an additional \$10^{-2}\$ for the one call to print. Suddenly the code runs in 1/5 of the time. So yes, deferring to print for string manipulation is normally a bad habit. \$\endgroup\$
    – Peilonrayz
    Commented Oct 3, 2021 at 20:03
  • \$\begingroup\$ For more context on you can read one of rolfl's answers on Software Engineering. \$\endgroup\$
    – Peilonrayz
    Commented Oct 3, 2021 at 20:04
  • \$\begingroup\$ Strangely this reminds me of an old answer I did on SO regarding a bowtie pattern - diamonds and bowties must be a common thing these days... \$\endgroup\$ Commented Oct 4, 2021 at 13:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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