#### 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()`:

```python
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`.

```python
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.

```python
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.

```python
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.

```python
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.

```python
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]]`.

```python
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](https://docs.python.org/3/tutorial/datastructures.html#list-comprehensions).

```python
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.
```python
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.