Skip to main content
Expand section on comments
Source Link
Mark H
  • 2.3k
  • 13
  • 19

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.

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.

Source Link
Mark H
  • 2.3k
  • 13
  • 19

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.

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.