10
\$\begingroup\$
def printTrg(rows):
    r1 = [1]
    r2 = [1, 1]
    trg = [r1, r2]
    r = []
    if rows == 1:
        r1[0] = str(r1[0])
        print(' '.join(r1))
    elif rows == 2:
        for o in trg:
            for a in range(len(o)):
                o[a] = str(o[a])
            print((' ')*(2-(a+1)), (' '.join(o)))
    else:
        for i in range(2, rows):
            trg.append([1]*i)
            for n in range(1, i):
                trg[i][n] = (trg[i-1][n-1]+trg[i-1][n])
            trg[i].append(1)
        for x in range(len(trg)):
            for y in trg[x]:
                s = str(y)
                r.append(s)
            print((' ')*(rows-(x+1)), (' ' .join(r)))
            r = []

I've been learning Python for a month, and developed this program that prints out the Pascal triangle with the number of rows you want. It works fine with 5 rows.

For example, it prints:

     1
    1 1
   1 2 1
  1 3 3 1
 1 4 6 4 1

But with more rows, it begins to be a little unbalanced because you have more digits on each number.

For example, a 10 row triangle:`

          1 
         1 1
        1 2 1
       1 3 3 1
      1 4 6 4 1
     1 5 10 10 5 1
    1 6 15 20 15 6 1
   1 7 21 35 35 21 7 1
  1 8 28 56 70 56 28 8 1
 1 9 36 84 126 126 84 36 9 1

If you could help me with the output, it would be great.


EDIT:

def generate_pascals_triangle(rows):
    """ Returns a list of rows of a Pascal's Triangle with how many rows you want """
    triangle = [[1], [1, 1]]
    if rows == 1:
        return triangle[0]
    else:
        for row_number in range(2, rows):
            triangle.append([1]*row_number)
            for number in range(1, row_number):
                triangle[row_number][number] = (triangle[row_number-1][number-1]+triangle[row_number-1][number])
            triangle[row_number].append(1)
        return triangle

def difference_between_rows(row, next_row):
    """ Returns the difference between two rows in the formatted way """
    row_len = 0
    next_row_len = 0
    for number in row:
        string_number = str(number)
        row_len += (len(string_number)+1)
    for number in next_row:
        string_number = str(number)
        next_row_len += (len(string_number)+1)
    return (next_row_len-1) - (row_len-1)

def print_pascals_triangle(triangle):
    """ Prints the Pascal's Triangle previously generated by generate_pascals_triangle in a formatted form """
    for row in triangle:
        difference = int((difference_between_rows(row, triangle[len(triangle)-1]))/2)
        for number in range(len(row)):
            row[number] = str(row[number])
        print ((' ')*(difference), ' '.join(row))

if __name__ == '__main__': #credit to @alexwlchan    
    again = 'y'
    while again == 'y':
        rows = int(input('Enter the number of rows you want in your Pascal`s Triangle: '))
        print_pascals_triangle(generate_pascals_triangle(rows))
        again = input('Do it again ? <y/n> ')
\$\endgroup\$
7
  • \$\begingroup\$ Welcome to Code Review! It's an OK first question you have here, to make it even better you could provide example input/output of your program and explain a little bit what "Pascal Triangle" is (or provide a wikipedia link) for those unfamiliar with the subject. Anything you can do to help the reviewers understand and review your code is appreciated! \$\endgroup\$ Mar 2, 2014 at 15:46
  • \$\begingroup\$ Regarding "shortening" code that's not exactly what we do around here. We mostly prefer clean code which does not always have to be the same as short. In this case though, it is possible that the cleaner version also will be the shorter version. \$\endgroup\$ Mar 2, 2014 at 15:49
  • \$\begingroup\$ Nice edit you did, I just want to point out that "If you could help me with the output, it would be great." is a bit off-topic here as that would technically be modifying what the code does / adding a feature to the code, Code Review is more about how the code does something. You can expect answers about how you can clean up the code, just don't expect answers about how to fix that output (that being said, if our answerers are extra friendly today they might have an idea about that too :) ) \$\endgroup\$ Mar 2, 2014 at 16:32
  • \$\begingroup\$ I don't think you can really make the output work properly without knowing how many rows of the triangle you are going to be outputting. And if you do, the amount of spacing required is going to depend on the length of the largest integer you calculate, which could probably be computed mathematically but a simpler approach is to store your output in memory and then print it all at once with all the information you need. \$\endgroup\$
    – Thomas
    Mar 2, 2014 at 22:02
  • \$\begingroup\$ Actually the spaces required to centralize the rows just depends on te largest row that is the last one... \$\endgroup\$ Mar 2, 2014 at 22:31

2 Answers 2

19
\$\begingroup\$

Some brief comments:

  • Your variable names are quite short (often one letter), which makes the code harder to follow. Longer and more descriptive variable names would make it easier to read and debug (e.g., trg to triangle, r1 to row1).

    The input to the function printTrg is rows, but we also have variables like r, r1 and r2 which look like rows. Perhaps this would be better named as row_count?

    The main style guide for Python is PEP 8, which is very readable and friendly. It gives advice on how you choose variable names, functions, and so on. In particular, for function names, it says:

    Function names should be lowercase, with words separated by underscores as necessary to improve readability.

    So you should rename the function printTrg to be print_trg, or even better print_triangle, to match the Python conventions.

    There's also something called a docstring, which is used in Python functions to explain what the function is supposed to do. This usually occurs in triple quotes, directly below the def line. For example, you might write:

    def printTrg(rows):
        """Prints Pascal's triangle up to the (rows)th row."""
        ...
    

    There's a more detailed explanation of docstrings in PEP 257.

  • Right now the code which generates Pascal's triangle, and the code which prints it to the console, are tightly bound together. You might be better off splitting the two into two functions:

    • generate_pascals_triangle(row_count), which would return the list of lists trg which your function already creates.
    • print_pascals_triangle(triangle), which could take the output from generate and print it to the console.
  • The elif rows == 2 block is redundant, as the else block does exactly the same thing. The range(2, rows) never executes if rows is 2.

    While we're in the else block, I’m unclear why your range goes to rows in the first for loop, but to len(trg) in the second. Unless I've confused myself, these are always the same thing. And later in the same loop, you use rows to count the number of spaces you need.

  • The way you generate the next row of Pascal's triangle (insert a list of 1s, update the entries, then put an extra 1 on the end) could be simplified. Since you’re making a list for the entries in that row, you could use a list comprehension. (If you're not familiar with these, then there are lots of good explanations on Google.)

    Here's a simple example of a function which takes the previous row of Pascal's triangle, works out which row you're in now (by looking at the length of the previous row), then generates the new row with a list comprehension:

    def next_row(row):
        """Returns the next row of Pascal's triangle."""
        n = len(row)
        new_row = [row[0]] + [row[i] + row[i+1] for i in range(n - 1)] + [row[-1]]
        return new_row
    

    We're still doing essentially the same thing, but this looks cleaner, and we've split up some more of the logic. This can make life easier when it comes to debugging.

    This also works if we give it the first row [1], so we don't need to consider the cases rows == 1 and rows != 1 separately when we generate Pascal's triangle. Again, we're able to simplify some of the code.

  • As you’ve pointed out, things get out of line if you print a large number of rows, as two and three digit numbers come in to play. If you split this into two functions, then you can play around with the printing without affecting the arithmetic (which works perfectly).

So with all that in mind, here's how I might rewrite your code. First I have the function which I mentioned above, which takes one row of Pascal's triangle and returns the next:

def next_row(row):
    """Returns the next row of Pascal's triangle."""
    n = len(row)
    new_row = [row[0]] + [row[i] + row[i+1] for i in range(n - 1)] + [row[-1]]
    return new_row

Next, we have a function which just generates the entries of Pascal's triangle. Since we're not printing anything (that will come later), and the arithmetic for generating new rows is in another function, this is much shorter than before:

def generate_pascals_triangle(row_count):
    """Returns the entries of Pascal's triangle."""
    row1 = [1]
    triangle = [row1]
    for i in range(1, row_count):
        triangle.append(next_row(triangle[-1]))
    return triangle

If we wanted, we could supply the first entry (or even a whole first row) as an argument. You could see what happens if you make 2 the top entry, or started several levels deep. This is left for you to play with. (You may find this explanation of optional and named arguments useful.)

Finally, we come to the printing problem. We can use the logic from your else block, almost unmodified (just with more descriptive variable names):

def print_pascals_triangle(triangle):
    """Prints the entries of Pascal's triangle."""
    for row_no in range(len(triangle)):
        row = triangle[row_no]
        printed_row = []
        for entry in row:
            printed_row.append(str(entry))
        print (' ' * (len(triangle) - (row_no + 1)), ' '.join(printed_row))

If you wanted, that could become a little shorter using list comprehensions (I leave that as an exercise).

Here's an idea for how you could solve the spacing issue: first, find the longest number in your triangle. (You don't need to examine every entry: which row will always contain the longest entry?) Then, add spaces to every number you print, so that they're always the same length.

For example, if the longest entry was 2704156 (which you get in a 25-triangle), you would transform

1 -> _ _ _ 1 _ _ _

15 -> _ _ 1 5 _ _ _

165 -> _ _ 1 6 5 _ _

and so on. I haven't tried that, so I don't know how good it looks, but it might be something for you to get you started.


ETA: One more comment, based on your updated code:

At the end of the script, you have a little “interactive” section, which gets the user to give you a row count, and then you print their Pascal’s triangle.

If you load this file with import pascal-triangle in a larger script, then you immediately get asked to start drawing Pascal triangles. This can be unhelpful (imagine if everything you imported did that!).

Instead, you can put this code inside a special if statement:

if __name__ == '__main__':
    again = 'y'
    while again == 'y':
        rows = int(input('Enter the number of rows you want in your Pascal`s Triangle: '))
        print_pascals_triangle(generate_pascals_triangle(rows))
        again = input('Do it again ? <y/n> ')

Any code within this if statement is only used if the script is run directly; that is, if somebody types

$ python pascal-triangle.py

at a command prompt. If it gets loaded as import pascal-triangle in another script, then any code inside this if statement won’t be run. This means that you can reuse these functions later without any hassle.

You can find out more in this Stack Overflow question: https://stackoverflow.com/questions/419163/what-does-if-name-main-do

\$\endgroup\$
5
  • \$\begingroup\$ Man youre awesome, I'll try to do what you said, and get a more readable code. When I did this st first I was so lazy but i got it in 30 minutes. I'm pretty familiar with lists comprehensions but I'm not used to use that. pscl v2.0 is coming... \$\endgroup\$ Mar 2, 2014 at 18:39
  • \$\begingroup\$ when you use row[-1] what you want to get with minus 1 ? I think creating a whole function to get the next row is a bit unnecessary, and using that back in the generate function will be almost as long as before ... \$\endgroup\$ Mar 2, 2014 at 19:34
  • \$\begingroup\$ @matheussilvapb: negative indexes in Python (such as my_list[-1]) count from the end of the list. So my_list[-1] gets the last item, my_list[-2] gets the second from the end, and so on. \$\endgroup\$
    – alexwlchan
    Mar 2, 2014 at 19:41
  • 2
    \$\begingroup\$ I have converted the OP's answer into an edit. \$\endgroup\$
    – Jamal
    Mar 2, 2014 at 21:57
  • \$\begingroup\$ @alexwlchan awesome man, do you think I'm doing fine for a month with python ? \$\endgroup\$ Mar 2, 2014 at 22:21
7
\$\begingroup\$

Reviewing your edited code (Rev 7):

Good job — it works!

One nitpick: In print_pascals_triangle(), you use int() to round the result of division down to the nearest integer:

difference = int(difference_between_rows(…, …)/2)

To do integer division (rounding towards -∞), use the // operator for better performance and readability:

difference = difference_between_rows(…, …) // 2

Easier way to centre a row

Basically, your goal is to ensure that every row is printed centre-aligned. You've done it by calculating how many spaces to prepend to each row. The easier way is to figure out the width of the longest line, then centre-align everything based on that width. An easy way to centre-align text is to call str.center().

def print_pascals_triangle(triangle):
    def format_row(row):
        return ' '.join(map(str, row))
    triangle_width = len(format_row(triangle[-1]))
    for row in triangle:
        # Print each row, enter-aligned with the computed width
        print(format_row(row).center(triangle_width))

Eliminating difference_between_rows() makes it simpler and more efficient.

Making it more… triangular?

For larger inputs, the shape starts looking less like a triangle.

Enter the number of rows you want in your Pascal`s Triangle: 16
                                1                                
                               1 1                               
                              1 2 1                              
                             1 3 3 1                             
                            1 4 6 4 1                            
                          1 5 10 10 5 1                          
                        1 6 15 20 15 6 1                         
                       1 7 21 35 35 21 7 1                       
                     1 8 28 56 70 56 28 8 1                      
                   1 9 36 84 126 126 84 36 9 1                   
               1 10 45 120 210 252 210 120 45 10 1               
             1 11 55 165 330 462 462 330 165 55 11 1             
           1 12 66 220 495 792 924 792 495 220 66 12 1           
       1 13 78 286 715 1287 1716 1716 1287 715 286 78 13 1       
   1 14 91 364 1001 2002 3003 3432 3003 2002 1001 364 91 14 1    
1 15 105 455 1365 3003 5005 6435 6435 5005 3003 1365 455 105 15 1

What's worse, the display no longer shows off how each entry is the sum of two neighbouring entries in the row above. Ideally, it should look like

   1     9    36
    ↘︎ ⊕ ↙︎ ↘︎ ⊕ ↙︎      
1    10    45 

To achieve that spacing, you want every element to be given the same width — namely, the width necessary to accommodate the largest number in the triangle. The code above can be tweaked to implement that. Use a similar technique to ensure that each element is centred within the width allotted to it.

def print_pascals_triangle(triangle):
    largest_element = triangle[-1][len(triangle[-1]) // 2]
    element_width = len(str(largest_element))
    def format_row(row):
        return ' '.join([str(element).center(element_width) for element in row])
    triangle_width = len(format_row(triangle[-1]))
    for row in triangle:
        print(format_row(row).center(triangle_width))
Enter the number of rows you want in your Pascal`s Triangle: 16
                                      1                                        
                                    1    1                                     
                                 1    2    1                                   
                               1    3    3    1                                
                            1    4    6    4    1                              
                          1    5    10   10   5    1                           
                       1    6    15   20   15   6    1                         
                     1    7    21   35   35   21   7    1                      
                  1    8    28   56   70   56   28   8    1                    
                1    9    36   84  126  126   84   36   9    1                 
             1    10   45  120  210  252  210  120   45   10   1               
           1    11   55  165  330  462  462  330  165   55   11   1            
        1    12   66  220  495  792  924  792  495  220   66   12   1          
      1    13   78  286  715  1287 1716 1716 1287 715  286   78   13   1       
   1    14   91  364  1001 2002 3003 3432 3003 2002 1001 364   91   14   1     
 1    15  105  455  1365 3003 5005 6435 6435 5005 3003 1365 455  105   15   1
\$\endgroup\$
3
  • \$\begingroup\$ Just did this new way... can you explain more of the map() function and the string.format() function ? \$\endgroup\$ Mar 3, 2014 at 16:35
  • 1
    \$\begingroup\$ map(func, iterable) returns another iterable that is a result of calling the function on each item. For example, map(str, [1, 2, 3]) produces ["1", "2", "3"]. string.format() is a bit like the sprintf() function in C; it's best just to read the link to the string.format() documentation I linked to above. \$\endgroup\$ Mar 3, 2014 at 18:24
  • \$\begingroup\$ You don't need str.format() after all! I forgot that str.center() existed. \$\endgroup\$ Mar 4, 2014 at 7:32

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