# Pascal's Triangle Generator

Pascal's triangle is a simple and effective way to expand a set of brackets in the form (a + b)ⁿ.

In my code the user is asked an input for what order (n) they want and it outputs Pascal's triangle.

## The Code

def get_super(x):  # function to get superscript char.
normal = "0123456789"
super_s = "⁰¹²³⁴⁵⁶⁷⁸⁹"
res = x.maketrans(''.join(normal), ''.join(super_s))
return x.translate(res)

# the history_variable will be the variable returned when function is called. It will contain each co-efficient of every row.
# the number of rows that the history_variable returns is provided by parameters(num)
# I created save_variable as a variable I can use to store the previous row because I need it to create the next row.
# current_variable is a variable that will contain the current row being made.

def basic_pascals(num):
history_variable = [[1], [1, 1]]
save_variable = [1, 1]
current_variable = []
amount = 0
# if the number given is 0 just return 1 as that is the first row.
if num == 0:
return([1])
# if the number given is 1 return [1, 1] as those are the coefficients of (a+b)
elif num == 1:
return([1, 1])
# otherwise we create a for loop that will loop through num-1 iterations - I of every loop here as the making of one row
# in that loop we create a for loop over the save_variable and save_variable[1:] which will let us loop through every possible pair.
# the reason I do this is because each co-efficient in the new row is equal to the addition of the two co_efficients directly above it in the previous row.
# I then add every sum of every pair to the current-variable.
# add 1 to the start and end and then I have the co-efficients of the row.
# equate save_variable to current_variable
# then append save_variable to history_variable
# it repeats itself and finally history_variable is a list of lists each list containing the co-efficients of every row.
for i in range(num-1):
for item in zip(save_variable, save_variable[1:]):
amount += sum(item)
current_variable.append(amount)
amount = 0
current_variable.append(1)
current_variable.insert(0, 1)
save_variable = current_variable
current_variable = []
history_variable.append(save_variable)
return history_variable
# this is essentially adding the a's and b's to the co-efficients
# specify the order you want and if the order == 0 or 1 then it just prints out 1 or (1a + 1b)
# otherwise we make variable co-efficients and call basic_pascals to it.
# power a will equal the highest power possible depending on the order of the row, i.
# power b will equal 0. As you move through every term in a row, the power in a decreases and b increases
# rest is forming f"string" to add it to the co-efficients

def pascals_triangle():
# the n value of (a+b)^n
order = int(input("Enter the order(n) you would like for (a+b)^n: "))
spacing = order*10
if order == 0:
print(1)
if order == 1:
print(f"a{get_super('1')} + b{get_super('1')}")
else:
co_efficients = basic_pascals(order)
for i, row in enumerate(co_efficients):
power_a = i
power_b = 0
result = f""
for item in row:
a = f"a{power_a}"
b = f"b{power_b}"
if i == 0:
result += f"{item}"
elif power_a == 0:
result += f"{item}{get_super(b)} + "
elif power_b == 0:
result += f"{item}{get_super(a)} + "
else:
result += f"{item}{get_super(a)}{get_super(b)} + "
power_a -= 1
power_b += 1
result = result.strip(" + ")
print(result.center(spacing))
pascals_triangle()


## Output

### Calling basic_pascals(8)

[[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]]


### Calling pascals_triangle(8)

                                       1
1a¹ + 1b¹
1a² + 2a¹b¹ + 1b²
1a³ + 3a²b¹ + 3a¹b² + 1b³
1a⁴ + 4a³b¹ + 6a²b² + 4a¹b³ + 1b⁴
1a⁵ + 5a⁴b¹ + 10a³b² + 10a²b³ + 5a¹b⁴ + 1b⁵
1a⁶ + 6a⁵b¹ + 15a⁴b² + 20a³b³ + 15a²b⁴ + 6a¹b⁵ + 1b⁶
1a⁷ + 7a⁶b¹ + 21a⁵b² + 35a⁴b³ + 35a³b⁴ + 21a²b⁵ + 7a¹b⁶ + 1b⁷
1a⁸ + 8a⁷b¹ + 28a⁶b² + 56a⁵b³ + 70a⁴b⁴ + 56a³b⁵ + 28a²b⁶ + 8a¹b⁷ + 1b⁸


## Improvements

I want to make my code a bit more concise and easier to understand. I even have to think about why I do certain things sometimes.

• Coefficients can be calculated quickly by a closed formula (binomial coefficient). It is available in the standard Python library as math.comb(n,k). Aug 31, 2022 at 11:17
• Thanks for the feedback guys. Honestly when I am first trying to code stuff like this out I don't even think about any of these more better syntaxes like comprehensions. I tend to always make more code then I need to just because I can understand it better and actually get something that works. I guess it just comes with more and more practice before I can know when to use these types of things. Aug 31, 2022 at 14:54
• It absolutely is a matter of practice and learning what tools are out there. I'm learning new things from the other answers here, as well. Sep 1, 2022 at 3:48

#### 0. Delete all the comments

These paragraph-length comments make the code very hard to read. Comments should be a last resort for understanding the code since programmers only read them when they can't figure out what the code is doing. Well-written code doesn't just make the computer do the correct thing, but is also easy to understand by humans. Let's get rid of the comments and simplify the code.

The fewer words written, the fewer chances for mistakes.

I should expand on this. The best advice I've heard is this: comments should only explain what the code cannot. Comments that describe what the code is doing are useless because a programmer can just read the code. Here are some examples of useful types of comments:

• // This function implements <name of obscure algorithm> (<wikipedia link>)
• // You would think that doing XXX would be the obvious solution, but that doesn't work because of YYY, which means we have to do ZZZ.
• // This logic is required because of <business requirement> which is documented in <policy manual> on page 372.

From my own experience, if I'm reading code I wrote some time ago and it takes me more than a few seconds to understand what I wrote, that's a good place for a short comment to explain what I was trying to do. Either that or it's a good place to rewrite the code into something more comprehensible. One of the longest comments I've ever written was next to a continue statement to explain why that loop iteration could be skipped.

#### 1. Variable names

Picking accurate and specific variable names makes reasoning about the code easier. Let's look at basic_pascals():

def basic_pascals(num):
history_variable = [[1], [1, 1]]
save_variable = [1, 1]
current_variable = []
amount = 0
if num == 0:
return([1])
elif num == 1:
return([1, 1])

for i in range(num-1):
for item in zip(save_variable, save_variable[1:]):
amount += sum(item)
current_variable.append(amount)
amount = 0
current_variable.append(1)
current_variable.insert(0, 1)
save_variable = current_variable
current_variable = []
history_variable.append(save_variable)
return history_variable


The argument num is the degree of the last polynomial in the triangle, so use degree instead. The variable history_variable is the Pascal's Triangle, so let's rename it triangle. By tracking what happens to save_variable through the function, we see that it is always equal to the current last row of history_variable, so a natural name is last_row. The variable current_variable is the next row of the triangle being constructed, so let's call it next row.

def basic_pascals(degree):
triangle = [[1], [1, 1]]
last_row = [1, 1]
next_row = []
amount = 0
if degree == 0:
return([1])
elif degree == 1:
return([1, 1])

for i in range(degree-1):
for item in zip(last_row, last_row[1:]):
amount += sum(item)
next_row.append(amount)
amount = 0
next_row.append(1)
next_row.insert(0, 1)
last_row = next_row
next_row = []
triangle.append(last_row)
return triangle


#### 2. Create variables near where they are needed

Since you create the variables next_row, last_row, and amount outside of the loops, you need to reset them at the end of the loops. If you move these to inside the loop, then they will be reset automatically at the beginning of each loop and you can delete the lines that reset the variables.

def basic_pascals(degree):
triangle = [[1], [1, 1]]
if degree == 0:
return([1])
elif degree == 1:
return([1, 1])

for i in range(degree-1):
next_row = []
last_row = triangle[-1]
for item in zip(last_row, last_row[1:]):
amount = 0
amount += sum(item)
next_row.append(amount)
next_row.append(1)
next_row.insert(0, 1)
triangle.append(next_row)
return triangle


Now we can see that amount in the innermost loop is always equal to sum(item), so let's just use the latter expression.

def basic_pascals(degree):
triangle = [[1], [1, 1]]
if degree == 0:
return([1])
elif degree == 1:
return([1, 1])

for i in range(degree-1):
next_row = []
last_row = triangle[-1]
for item in zip(last_row, last_row[1:]):
next_row.append(sum(item))
next_row.append(1)
next_row.insert(0, 1)
triangle.append(next_row)
return triangle


#### 3. Expressive loop conditions

How do we know we are done constructing the triangle? An n-th degree Pascal's Triangle has n+1 rows. This makes for a more expressive loop condition that lets the programmer know when the construction is complete. Most of the time, if a loop variable is not used, i in this case, that is a good indication that there is a better way to write it.

def basic_pascals(degree):
triangle = [[1], [1, 1]]
if degree == 0:
return([1])
elif degree == 1:
return([1, 1])

while len(triangle) < degree + 1:
next_row = []
last_row = triangle[-1]
for item in zip(last_row, last_row[1:]):
next_row.append(sum(item))
next_row.append(1)
next_row.insert(0, 1)
triangle.append(next_row)
return triangle


#### 4. Consistent return values

Right now, there are two possible return types: a list of lists [[]] if degree >= 2 and a list [] otherwise. If you make the types of all possible return values the same, then any code that calls this function can be simpler because it only has to handle one data type. This function should construct the full triangle, so all return statements should return a list of lists representing the full triangle.

def basic_pascals(degree):
triangle = [[1], [1, 1]]
if degree == 0:
return [[1]]
elif degree == 1:
return [[1], [1, 1]]

while len(triangle) < degree + 1:
next_row = []
last_row = triangle[-1]
for item in zip(last_row, last_row[1:]):
next_row.append(sum(item))
next_row.append(1)
next_row.insert(0, 1)
triangle.append(next_row)
return triangle


Now, pascals_triangle() doesn't need special cases for degrees 0 and 1.

#### 5. Removing special cases.

Now that all return values return the same data type, do we even need the special cases for degree=0 and degree=1? Let's delete the initial if block and replace the initial value of triangle with [[1]].

def basic_pascals(degree):
triangle = [[1]]

while len(triangle) < degree + 1:
next_row = []
last_row = triangle[-1]
for item in zip(last_row, last_row[1:]):
next_row.append(sum(item))
next_row.append(1)
next_row.insert(0, 1)
triangle.append(next_row)
return triangle


This still returns the correct answer.

#### 6. Replace for ... append with list comprehensions

The inner for loop can be expressed as a single line to make the full list. This is called a list comprehension.

def basic_pascals(degree):
triangle = [[1]]

while len(triangle) < degree + 1:
last_row = triangle[-1]
next_row = [sum(item) for item in zip(last_row, last_row[1:])]
next_row.append(1)
next_row.insert(0, 1)
triangle.append(next_row)
return triangle


We can even incorporate the ones on the start and end.

def basic_pascals(degree):
triangle = [[1]]

while len(triangle) < degree + 1:
last_row = triangle[-1]
next_row = [1] + [sum(item) for item in zip(last_row, last_row[1:])] + [1]
triangle.append(next_row)

return triangle


I added space before the return statement to emphasize the three steps: initialize triangle, construct triangle, return triangle.

#### Other parts of the code

In pascals_triangle(), in addition to making similar changes as described above, you can create a function that takes co_efficient, power_a, and power_b as argument to make each term in the polynomial. This will simplify the loop and give you more freedom to get the notation correct.

Rather than trying to build the whole expression at once, it is simpler to build a list of terms (terms = ["1a³", "3a²b¹", "3a¹b²", "1b³"]) and then use " + ".join(terms) to create the full string. Then, you don't have to use strip() at the end.

• To be clear on #0: comments should explain tricky code or why a code does what it does, not just repeat what it does.
– qwr
Aug 31, 2022 at 19:31
• @qwr I've expanded on the comment section. Sep 1, 2022 at 13:45
• #0 could use a line or two about docstrings, as an alternative to throwing out the comments completely.
– Mast
Sep 1, 2022 at 20:09
• Another popular way to explain your excellent point about comments: code explains how the code works, comments explain why the code works. (Sorry, I'm treading slightly on @qwr's toes.) +1.
– J.G.
Sep 1, 2022 at 20:58

Mark H has given good feedback about the triangle generation. I'll try not to repeat that feedback.

# translate & maketrans

str.translate(table) is my favourite Python function, so I hate seeing it used improperly.

def get_super(x):  # function to get superscript char.
normal = "0123456789"
super_s = "⁰¹²³⁴⁵⁶⁷⁸⁹"
res = x.maketrans(''.join(normal), ''.join(super_s))
return x.translate(res)


First, ''.join(normal) is simply normal, and ''.join(super_s) is simply super_s. The ''.join(...) calls are busywork that does nothing but waste time.

def get_super(x):  # function to get superscript char.
normal = "0123456789"
super_s = "⁰¹²³⁴⁵⁶⁷⁸⁹"
res = x.maketrans(normal, super_s)
return x.translate(res)


Now, this function is called many, many times. Each time, it computes res = x.maketrans(normal, super_s), and normal and super_s are never different, so the result res is always the same. Again, this is wasting time. We should compute this once, outside of the function.

SUPERSCRIPT_TRANS_TABLE = str.maketrans("0123456789", "⁰¹²³⁴⁵⁶⁷⁸⁹")

def get_super(s: str) -> str:
"""
Replace the digits 0 through 9 in a string with superscript equivalents.
All other characters remain unchanged.

>>> get_super(' a1 b2 c3 ')
' a¹ b² c³ '
"""

return s.translate(SUPERSCRIPT_TRANS_TABLE)


I've additionally replaced # function to get superscript char. with a docstring describing what the function does, and added type-hints to the function signature. This is much more useful, since a user can actually type help(get_super) at an interactive prompt and get a description of what the function does, and how to use it, including an example of calling the function.

>>> help(get_super)
Help on function get_super in module __main__:

get_super(s: str) -> str
Replace the digits 0 through 9 in a string with superscript equivalents.
All other characters remain unchanged.

>>> get_super(' a1 b2 c3 ')
' a¹ b² c³ '

>>>


The doctest module can even be used to read the "examples" from the docstrings, execute them, and verify the actual output matches the example output.

>>> import doctest
>>> doctest.testmod(verbose=True)
Trying:
get_super(' a1 b2 c3 ')
Expecting:
' a¹ b² c³ '
ok
__main__
1 items passed all tests:
1 tests in __main__.get_super
1 tests in 2 items.
1 passed and 0 failed.
Test passed.
TestResults(failed=0, attempted=1)

>>>


You'd usually put this in a main-guard, without the verbose flag, so that nothing is displayed when all tests pass.

if __name__ == '__main__':
import doctest
doctest.testmod()


I'd prefer a different function name, like digits_to_superscript or to_superscript or even simply superscript instead of get_super.

# Bug

Enter the order(n) you would like for (a+b)^n: 1
a¹ + b¹


This is not a triangle, since 1 is not printed centered above it! It also lacks the coefficients in front of the variables (eg, 1a¹ + 1b¹) of degree ≥ 2 output.

# Centering

10*order is an interesting guess at how much space is needed for the triangle to be centered in, but it will not work for the general case. For order 8, it is 10 characters too many. For orders higher than 10, your triangle can have terms like  + 63205303218876a²⁴b²⁵ ... which has significantly more than 10 character.

You've already computed the terms for the last row of the triangle before you start printing it. You could easily determine exactly how long that row is:

...
...
last_row = co_efficients[-1]
spacing = sum(map(len, map(str, last_row)))                # Coefficients
spacings += 2 * sum(map(len, map(str, range(1, order+1)))) # Exponents
spacings += 2 * order                                      # a's & b's
spacings += 3 * order                                      # " + "
...


Alternately, without trying to compute the length from the cooefficients, you could generate and store all of the strings, then simply measure the length of the last one and print out all strings centered to that length.

# Reworked code

Here is my rewrite of the code, in Python 3.10. It uses infinite generators for both Pascal's Triangle coefficients and polynomial generation. pairwise() is now part of itertools, and removes the need for the inefficient zip(save_variable, save_variable[1:]) construct, which makes an unnecessary copy of the elements of save_variable for the [1:] slicing operation. The match statement is a new Python 3.10 construct ... and undoubtedly overkill for this usage, but it was fun to use. ;^)

from itertools import pairwise, islice
from typing import Any, Iterator

SUPERSCRIPT_TRANS_TABLE = str.maketrans("0123456789", "⁰¹²³⁴⁵⁶⁷⁸⁹")

def superscript(obj: Any) -> str:
"""
Replace the digits 0 through 9 in an object's string representation with
superscript equivalents.  All other characters remain unchanged.

>>> superscript(' a1 b2 c3 ')
' a¹ b² c³ '
"""

return str(obj).translate(SUPERSCRIPT_TRANS_TABLE)

def center(lines: Iterator[str]) -> Iterator[str]:
"""
Collect a sequence of strings, determine the maximum length,
and yield the sequence of strings centered within the width
of the longest string.
"""

lines = list(lines)
max_width = max(map(len, lines))
return (line.center(max_width) for line in lines)

def pascals_triangle_generator() -> Iterator[list[int]]:
"""
A pascal triangle coefficent row generator

Returns an infinite generator of Pascal's triangle rows
"""

row = [1]

while True:
yield row
row = [1] + [sum(terms) for terms in pairwise(row)] + [1]

def pascals_triangle_polynomials(var1: str, var2: str) -> Iterator[str]:
"""
A pascal triangle polynomial generator

Returns an infinite generator of polynomials generated using
Pascal's triangle.
"""

def term(args):
match args:                                 # Note: Requires Python 3.10
case coeff, 0, 0:
return str(coeff)
case coeff, m, 0:
return f"{coeff}{var1}{superscript(m)}"
case coeff, 0, n:
return f"{coeff}{var2}{superscript(n)}"
case coeff, m, n:
return f"{coeff}{var1}{superscript(m)}{var2}{superscript(n)}"

for row, coeffs in enumerate(pascals_triangle_generator(), 1):
exponents = range(row)
yield " + ".join(map(term, zip(coeffs, exponents[::-1], exponents)))

def basic_pascals(degree: int) -> list[list[int]]:
"""
Return Pascal's triangle of the given degree

>>> basic_pascals(4)
[[1], [1, 1], [1, 2, 1], [1, 3, 3, 1], [1, 4, 6, 4, 1]]
"""

return list(islice(pascals_triangle_generator(), degree + 1))

def pascals_triangle(degree: int, var1: str, var2: str) -> None:
"""
Print Pascal's Triangle, of the given degree, with the given variables,
centered based on the length of the longest line

>>> pascals_triangle(4, "x", "y")
1
1x¹ + 1y¹
1x² + 2x¹y¹ + 1y²
1x³ + 3x²y¹ + 3x¹y² + 1y³
1x⁴ + 4x³y¹ + 6x²y² + 4x¹y³ + 1y⁴
"""

print(*center(islice(pascals_triangle_polynomials(var1, var2), degree + 1)),
sep='\n')

if __name__ == '__main__':
import doctest
doctest.testmod()

print(basic_pascals(8))
pascals_triangle(8, "a", "b")


I'd already half-written this before the other answers rolled in, so I'll attempt to minimise overlap:

This is a good application for iterators, which you don't use.

Though he didn't describe doing so, AJNeufeld has correctly added PEP484 typehints and you should do the same.

With minimal effort, you can improve your formatting to omit redundant 1 coefficients and 1 exponents.

Prefer tuples over lists for immutable data.

## Suggested

from string import digits
from typing import Iterator

EXPONENTS = str.maketrans(digits, "⁰¹²³⁴⁵⁶⁷⁸⁹")

def pascal_coefficients(num: int) -> Iterator[tuple[int, ...]]:
row = 1,

for y in range(num+1):
yield row
row = (
1,
*(sum(row[x: x+2]) for x in range(y)),
1,
)

def format_term(letter: str, power: int) -> str:
if power == 0:
return ''
if power == 1:
return letter
exp_str = str(power).translate(EXPONENTS)
return letter + exp_str

def format_row(row: tuple[int, ...]) -> Iterator[str]:
y = len(row)
for x, n in enumerate(row):
result = format_term('a', y - x - 1) + format_term('b', x)
if n != 1 or not result:
result = f'{n}{result}'
yield result

def pascals_triangle_lines(order: int) -> Iterator[str]:
for row in pascal_coefficients(order):
yield ' + '.join(format_row(row))

def pascals_triangle(order: int) -> Iterator[str]:
*others, last = pascals_triangle_lines(order)
for other in others:
yield other.center(len(last))
yield last

def main() -> None:
order = int(input("Enter the order(n) you would like for (a+b)^n: "))
print('\n'.join(pascals_triangle(order)))

if __name__ == '__main__':
main()


## Output

Enter the order(n) you would like for (a+b)^n: 11
1
a + b
a² + 2ab + b²
a³ + 3a²b + 3ab² + b³
a⁴ + 4a³b + 6a²b² + 4ab³ + b⁴
a⁵ + 5a⁴b + 10a³b² + 10a²b³ + 5ab⁴ + b⁵
a⁶ + 6a⁵b + 15a⁴b² + 20a³b³ + 15a²b⁴ + 6ab⁵ + b⁶
a⁷ + 7a⁶b + 21a⁵b² + 35a⁴b³ + 35a³b⁴ + 21a²b⁵ + 7ab⁶ + b⁷
a⁸ + 8a⁷b + 28a⁶b² + 56a⁵b³ + 70a⁴b⁴ + 56a³b⁵ + 28a²b⁶ + 8ab⁷ + b⁸
a⁹ + 9a⁸b + 36a⁷b² + 84a⁶b³ + 126a⁵b⁴ + 126a⁴b⁵ + 84a³b⁶ + 36a²b⁷ + 9ab⁸ + b⁹
a¹⁰ + 10a⁹b + 45a⁸b² + 120a⁷b³ + 210a⁶b⁴ + 252a⁵b⁵ + 210a⁴b⁶ + 120a³b⁷ + 45a²b⁸ + 10ab⁹ + b¹⁰
a¹¹ + 11a¹⁰b + 55a⁹b² + 165a⁸b³ + 330a⁷b⁴ + 462a⁶b⁵ + 462a⁵b⁶ + 330a⁴b⁷ + 165a³b⁸ + 55a²b⁹ + 11ab¹⁰ + b¹¹

• EXPONENTS[power] doesn't work for powers greater than 9. Aug 31, 2022 at 1:51
• @MarkH thank you; fixed Aug 31, 2022 at 11:51
• You're welcome. I tried the same thing while working on my answer; "surely it's just a table lookup ..." Aug 31, 2022 at 12:12