Pascal's Triangle Generator in Python So I've been working on a generator for Pascal's triangle in Python. Now, I wouldn't call myself a Python god, so my program is a lot longer than the very confusing ones on the internet. It's more of a logical approach to creating the triangle.

Here's the program:

def double_chunker(lst):
  leng = len(lst)
  for i in range(leng):
    if i == 0:
      yield [lst[0]]
    elif i == 1:
      yield [lst[0], lst[1]]
    elif i == leng:
      yield [lst[-1]]
    else:
      yield [lst[i-1], lst[i]]
  yield [lst[-1]]

def chunk_adder(lst):
  for i in lst:
    if len(i) == 1:
      yield i[0]
    else:
      yield sum(i)

def pascal_next(lst):
  return list(chunk_adder(double_chunker(lst)))

def pascal_triangle(rows):
  end = [[1]]
  for i in range(rows):
    end.append(pascal_next(end[-1]))
  return end


A simple go-through of how it works:


double_chunker() splits up a row of Pascal's triangle into the pairs of numbers you would use when adding up to determine the numbers in the next row. This algorithm is little jerry-rigged - I had to add some special exceptions for some numbers on the end of the row to make it work properly.
chunk_adder() adds together a list of chunks generated by double_chunker to determine the next row in the Pascal sequence.
pascal_next()combines both double_chunker() and chunk_adder() to, when given one row in Pascal's triangle, determine the next row in the triangle.
pascal_triangle() iteratively creates rows of Pascal's triangle using pascal_next().


So, here are some of my questions:


Is there anything in my program that seems redundant, repetitive, or can be shortened?
Is there any better code practices I should be employing and am not?


And obviously, as always, feel free to provide any other feedback you may have. Thanks in advance!
def chunk_adder(lst):
  for i in lst:
    if len(i) == 1:
      yield i[0]
    else:
      yield sum(i)



sum can happilly consume iterable of size 1, it can even consume iterable of size 0:

&gt;&gt;&gt; sum([1])
1
&gt;&gt;&gt; sum([])
0


So you can simplify it to:

def chunck_adder(iterable):
    for element in iterable:
        yield sum(element)


Which is simply

def chunck_adder(iterable):
    yield from map(sum, iterable)


So you could simplify pascal_next instead:

def pascal_next(lst):
    return list(map(sum, double_chunker(lst)))





def double_chunker(lst):
  leng = len(lst)
  for i in range(leng):
    if i == 0:
      yield [lst[0]]
    elif i == 1:
      yield [lst[0], lst[1]]
    elif i == leng:
      yield [lst[-1]]
    else:
      yield [lst[i-1], lst[i]]
  yield [lst[-1]]



The intent is pretty much the same than the pairwise recipe from itertools. Except you want to yield the first and last element as well.

Here you have two possibilities:


either yield them manually:

import itertools

def double_chunker(lst):
    if not lst:
        return
    a, b = itertools.tee(lst)
    next(b, None)

    yield [lst[0]]
    yield from zip(a, b)
    yield [lst[-1]]


But this forces the argument to be a list, or at least to know if its empty and to implement __getitem__.
or add boundary values to your input so pairwise can work properly:

import itertools


def pairwise(iterable):
    a, b = itertools.tee(iterable)
    next(b, None)
    return zip(a, b)


def double_chuncker(iterable):
    extended = itertools.chain([0], iterable, [0])
    return pairwise(extended)


Which I recommend because it happily consume any iterable.





def pascal_triangle(rows):
  end = [[1]]
  for i in range(rows):
    end.append(pascal_next(end[-1]))
  return end



Instead of relying on the list being constructed, I would explicitly store the current row. I would also turn this into an infinite generator because it really is and maybe provide an helper function for convenience:

def pascal_triangle():
    row = [1]
    while True:
        yield row
        row = pascal_next(row)


def pascal_triangle_up_to(n):
    return list(itertools.islice(pascal_triangle(), n))




Full code:

import itertools


def pairwise(iterable):
    a, b = itertools.tee(iterable)
    next(b, None)
    return zip(a, b)


def double_chuncker(iterable):
    extended = itertools.chain([0], iterable, [0])
    return pairwise(extended)


def pascal_next(iterable):
    return list(map(sum, double_chuncker(iterable)))


def pascal_triangle():
    row = [1]
    while True:
        yield row
        row = pascal_next(row)


def pascal_triangle_up_to(n):
    return list(itertools.islice(pascal_triangle(), n))


if __name__ == '__main__':
    # Testing
    for row in pascal_triangle():
        print(row, end='')
        if (input()):
            break