Pascal's Triangle Generator in Python

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP












4












$begingroup$


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:



  1. 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.


  2. chunk_adder() adds together a list of chunks generated by double_chunker to determine the next row in the Pascal sequence.


  3. 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.


  4. pascal_triangle() iteratively creates rows of Pascal's triangle using pascal_next().


So, here are some of my questions:



  1. Is there anything in my program that seems redundant, repetitive, or can be shortened?


  2. 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!










share|improve this question









$endgroup$
















    4












    $begingroup$


    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:



    1. 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.


    2. chunk_adder() adds together a list of chunks generated by double_chunker to determine the next row in the Pascal sequence.


    3. 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.


    4. pascal_triangle() iteratively creates rows of Pascal's triangle using pascal_next().


    So, here are some of my questions:



    1. Is there anything in my program that seems redundant, repetitive, or can be shortened?


    2. 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!










    share|improve this question









    $endgroup$














      4












      4








      4


      1



      $begingroup$


      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:



      1. 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.


      2. chunk_adder() adds together a list of chunks generated by double_chunker to determine the next row in the Pascal sequence.


      3. 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.


      4. pascal_triangle() iteratively creates rows of Pascal's triangle using pascal_next().


      So, here are some of my questions:



      1. Is there anything in my program that seems redundant, repetitive, or can be shortened?


      2. 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!










      share|improve this question









      $endgroup$




      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:



      1. 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.


      2. chunk_adder() adds together a list of chunks generated by double_chunker to determine the next row in the Pascal sequence.


      3. 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.


      4. pascal_triangle() iteratively creates rows of Pascal's triangle using pascal_next().


      So, here are some of my questions:



      1. Is there anything in my program that seems redundant, repetitive, or can be shortened?


      2. 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!







      python






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Jan 17 at 11:24









      connectyourchargerconnectyourcharger

      1586




      1586




















          4 Answers
          4






          active

          oldest

          votes


















          10












          $begingroup$


          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:



          >>> sum([1])
          1
          >>> 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





          share|improve this answer











          $endgroup$




















            5












            $begingroup$

            Names



            I am not fully convinced by the different function names but I have nothing better to suggest for the time being.



            Style



            Python has a Style Guide called PEP 8. It is an interesting read. The most significant impact for your code would be to use 4 spaces for each indentation level instead of 2.



            Simplify double_chunker



            In double_chunker, the following condition is never true:



            elif i == leng:
            yield [lst[-1]]


            Also, you don't need to handle explicitly the case:



            elif i == 1:
            yield [lst[0], lst[1]]


            as it is just a particular case for [lst[i-1], lst[i]] with i == 1.



            Simplify chunk_adder



            In chunk_adder, instead of:



            if len(i) == 1:
            yield i[0]
            else:
            yield sum(i)


            We can write:



            yield sum(i)


            Then, we could rewrite the function using generator expressions:



            def chunk_adder(lst):
            return (sum(i) for i in lst)


            Then, it looks like the function is not really needed. We could write:



            def pascal_next(lst):
            return [sum(i) for i in double_chunker(lst)]



            At this stage, we have:



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


            def pascal_next(lst):
            return [sum(i) for i in double_chunker(lst)]

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


            print(pascal_triangle(8))


            More simplification in double_chunker



            We could handle the case i == 0 before the loop rather than inside the loop. That could lead to a slightly different behavior when the input is an empty list but that case is not handled properly anyway (exception thrown).



            def double_chunker(lst):
            yield [lst[0]]
            for i in range(1, len(lst)):
            yield [lst[i-1], lst[i]]
            yield [lst[-1]]


            Then, it becomes obvious what we want to do: we want to iterate over all pairs of consecutive items in a list which is a problem common enough to find various solutions to it.






            share|improve this answer









            $endgroup$




















              3












              $begingroup$


              Is there any better code practices I should be employing and am not?




              • The first thing that caught my attention is the missing tests

              You should implement a few test cases to ensure that after changes the program does still work as intended



              Both the unittest module or doctest are good Python modules for testing, I have used the unittest as an example



              class PascalTriangleTest(unittest.TestCase):
              def test_triangle_0(self):
              self.assertEqual(
              pascal_triangle(0),
              [[1]]
              )

              def test_triangle_1(self):
              self.assertEqual(
              pascal_triangle(1),
              [[1], [1, 1]]
              )

              def test_triangle_2(self):
              self.assertEqual(
              pascal_triangle(2),
              [[1], [1, 1], [1, 2, 1]]
              )

              def test_triangle_3(self):
              self.assertEqual(
              pascal_triangle(3),
              [[1], [1, 1], [1, 2, 1], [1, 3, 3, 1]]
              )

              if __name__ == '__main__':
              unittest.main()


              • The second one would be the missing docstrings

              The comments below your code would be a good start to make the docstring for each function.



              See PEP257, for docstring conventions






              share|improve this answer









              $endgroup$




















                2












                $begingroup$


                Is there anything in my program that seems redundant, repetitive, or can be shortened?




                The 22 lines of double_chunker, chunk_adder, and pascal_next can be shortened to



                def pascal_next(lst):
                return [left + right for (left, right) in zip(lst + [0], [0] + lst)]





                share|improve this answer









                $endgroup$








                • 1




                  $begingroup$
                  Or return [sum(pair) for pair in zip(lst + [0], [0] + lst)] to make use of the built in sum
                  $endgroup$
                  – Ludisposed
                  Jan 17 at 14:47











                • $begingroup$
                  @Ludisposed, I deliberately chose not to do that because I regard it as a pessimisation.
                  $endgroup$
                  – Peter Taylor
                  Jan 17 at 15:28










                • $begingroup$
                  You could also omit the parenthesis: [left + right for left, right in zip(lst + [0], [0] + lst)].
                  $endgroup$
                  – Graipher
                  Jan 17 at 15:30










                Your Answer





                StackExchange.ifUsing("editor", function ()
                return StackExchange.using("mathjaxEditing", function ()
                StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix)
                StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
                );
                );
                , "mathjax-editing");

                StackExchange.ifUsing("editor", function ()
                StackExchange.using("externalEditor", function ()
                StackExchange.using("snippets", function ()
                StackExchange.snippets.init();
                );
                );
                , "code-snippets");

                StackExchange.ready(function()
                var channelOptions =
                tags: "".split(" "),
                id: "196"
                ;
                initTagRenderer("".split(" "), "".split(" "), channelOptions);

                StackExchange.using("externalEditor", function()
                // Have to fire editor after snippets, if snippets enabled
                if (StackExchange.settings.snippets.snippetsEnabled)
                StackExchange.using("snippets", function()
                createEditor();
                );

                else
                createEditor();

                );

                function createEditor()
                StackExchange.prepareEditor(
                heartbeatType: 'answer',
                autoActivateHeartbeat: false,
                convertImagesToLinks: false,
                noModals: true,
                showLowRepImageUploadWarning: true,
                reputationToPostImages: null,
                bindNavPrevention: true,
                postfix: "",
                imageUploader:
                brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
                contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
                allowUrls: true
                ,
                onDemand: true,
                discardSelector: ".discard-answer"
                ,immediatelyShowMarkdownHelp:true
                );



                );













                draft saved

                draft discarded


















                StackExchange.ready(
                function ()
                StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f211677%2fpascals-triangle-generator-in-python%23new-answer', 'question_page');

                );

                Post as a guest















                Required, but never shown

























                4 Answers
                4






                active

                oldest

                votes








                4 Answers
                4






                active

                oldest

                votes









                active

                oldest

                votes






                active

                oldest

                votes









                10












                $begingroup$


                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:



                >>> sum([1])
                1
                >>> 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





                share|improve this answer











                $endgroup$

















                  10












                  $begingroup$


                  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:



                  >>> sum([1])
                  1
                  >>> 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





                  share|improve this answer











                  $endgroup$















                    10












                    10








                    10





                    $begingroup$


                    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:



                    >>> sum([1])
                    1
                    >>> 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





                    share|improve this answer











                    $endgroup$




                    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:



                    >>> sum([1])
                    1
                    >>> 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






                    share|improve this answer














                    share|improve this answer



                    share|improve this answer








                    edited Jan 17 at 13:29

























                    answered Jan 17 at 13:23









                    Mathias EttingerMathias Ettinger

                    24.3k33184




                    24.3k33184























                        5












                        $begingroup$

                        Names



                        I am not fully convinced by the different function names but I have nothing better to suggest for the time being.



                        Style



                        Python has a Style Guide called PEP 8. It is an interesting read. The most significant impact for your code would be to use 4 spaces for each indentation level instead of 2.



                        Simplify double_chunker



                        In double_chunker, the following condition is never true:



                        elif i == leng:
                        yield [lst[-1]]


                        Also, you don't need to handle explicitly the case:



                        elif i == 1:
                        yield [lst[0], lst[1]]


                        as it is just a particular case for [lst[i-1], lst[i]] with i == 1.



                        Simplify chunk_adder



                        In chunk_adder, instead of:



                        if len(i) == 1:
                        yield i[0]
                        else:
                        yield sum(i)


                        We can write:



                        yield sum(i)


                        Then, we could rewrite the function using generator expressions:



                        def chunk_adder(lst):
                        return (sum(i) for i in lst)


                        Then, it looks like the function is not really needed. We could write:



                        def pascal_next(lst):
                        return [sum(i) for i in double_chunker(lst)]



                        At this stage, we have:



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


                        def pascal_next(lst):
                        return [sum(i) for i in double_chunker(lst)]

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


                        print(pascal_triangle(8))


                        More simplification in double_chunker



                        We could handle the case i == 0 before the loop rather than inside the loop. That could lead to a slightly different behavior when the input is an empty list but that case is not handled properly anyway (exception thrown).



                        def double_chunker(lst):
                        yield [lst[0]]
                        for i in range(1, len(lst)):
                        yield [lst[i-1], lst[i]]
                        yield [lst[-1]]


                        Then, it becomes obvious what we want to do: we want to iterate over all pairs of consecutive items in a list which is a problem common enough to find various solutions to it.






                        share|improve this answer









                        $endgroup$

















                          5












                          $begingroup$

                          Names



                          I am not fully convinced by the different function names but I have nothing better to suggest for the time being.



                          Style



                          Python has a Style Guide called PEP 8. It is an interesting read. The most significant impact for your code would be to use 4 spaces for each indentation level instead of 2.



                          Simplify double_chunker



                          In double_chunker, the following condition is never true:



                          elif i == leng:
                          yield [lst[-1]]


                          Also, you don't need to handle explicitly the case:



                          elif i == 1:
                          yield [lst[0], lst[1]]


                          as it is just a particular case for [lst[i-1], lst[i]] with i == 1.



                          Simplify chunk_adder



                          In chunk_adder, instead of:



                          if len(i) == 1:
                          yield i[0]
                          else:
                          yield sum(i)


                          We can write:



                          yield sum(i)


                          Then, we could rewrite the function using generator expressions:



                          def chunk_adder(lst):
                          return (sum(i) for i in lst)


                          Then, it looks like the function is not really needed. We could write:



                          def pascal_next(lst):
                          return [sum(i) for i in double_chunker(lst)]



                          At this stage, we have:



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


                          def pascal_next(lst):
                          return [sum(i) for i in double_chunker(lst)]

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


                          print(pascal_triangle(8))


                          More simplification in double_chunker



                          We could handle the case i == 0 before the loop rather than inside the loop. That could lead to a slightly different behavior when the input is an empty list but that case is not handled properly anyway (exception thrown).



                          def double_chunker(lst):
                          yield [lst[0]]
                          for i in range(1, len(lst)):
                          yield [lst[i-1], lst[i]]
                          yield [lst[-1]]


                          Then, it becomes obvious what we want to do: we want to iterate over all pairs of consecutive items in a list which is a problem common enough to find various solutions to it.






                          share|improve this answer









                          $endgroup$















                            5












                            5








                            5





                            $begingroup$

                            Names



                            I am not fully convinced by the different function names but I have nothing better to suggest for the time being.



                            Style



                            Python has a Style Guide called PEP 8. It is an interesting read. The most significant impact for your code would be to use 4 spaces for each indentation level instead of 2.



                            Simplify double_chunker



                            In double_chunker, the following condition is never true:



                            elif i == leng:
                            yield [lst[-1]]


                            Also, you don't need to handle explicitly the case:



                            elif i == 1:
                            yield [lst[0], lst[1]]


                            as it is just a particular case for [lst[i-1], lst[i]] with i == 1.



                            Simplify chunk_adder



                            In chunk_adder, instead of:



                            if len(i) == 1:
                            yield i[0]
                            else:
                            yield sum(i)


                            We can write:



                            yield sum(i)


                            Then, we could rewrite the function using generator expressions:



                            def chunk_adder(lst):
                            return (sum(i) for i in lst)


                            Then, it looks like the function is not really needed. We could write:



                            def pascal_next(lst):
                            return [sum(i) for i in double_chunker(lst)]



                            At this stage, we have:



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


                            def pascal_next(lst):
                            return [sum(i) for i in double_chunker(lst)]

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


                            print(pascal_triangle(8))


                            More simplification in double_chunker



                            We could handle the case i == 0 before the loop rather than inside the loop. That could lead to a slightly different behavior when the input is an empty list but that case is not handled properly anyway (exception thrown).



                            def double_chunker(lst):
                            yield [lst[0]]
                            for i in range(1, len(lst)):
                            yield [lst[i-1], lst[i]]
                            yield [lst[-1]]


                            Then, it becomes obvious what we want to do: we want to iterate over all pairs of consecutive items in a list which is a problem common enough to find various solutions to it.






                            share|improve this answer









                            $endgroup$



                            Names



                            I am not fully convinced by the different function names but I have nothing better to suggest for the time being.



                            Style



                            Python has a Style Guide called PEP 8. It is an interesting read. The most significant impact for your code would be to use 4 spaces for each indentation level instead of 2.



                            Simplify double_chunker



                            In double_chunker, the following condition is never true:



                            elif i == leng:
                            yield [lst[-1]]


                            Also, you don't need to handle explicitly the case:



                            elif i == 1:
                            yield [lst[0], lst[1]]


                            as it is just a particular case for [lst[i-1], lst[i]] with i == 1.



                            Simplify chunk_adder



                            In chunk_adder, instead of:



                            if len(i) == 1:
                            yield i[0]
                            else:
                            yield sum(i)


                            We can write:



                            yield sum(i)


                            Then, we could rewrite the function using generator expressions:



                            def chunk_adder(lst):
                            return (sum(i) for i in lst)


                            Then, it looks like the function is not really needed. We could write:



                            def pascal_next(lst):
                            return [sum(i) for i in double_chunker(lst)]



                            At this stage, we have:



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


                            def pascal_next(lst):
                            return [sum(i) for i in double_chunker(lst)]

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


                            print(pascal_triangle(8))


                            More simplification in double_chunker



                            We could handle the case i == 0 before the loop rather than inside the loop. That could lead to a slightly different behavior when the input is an empty list but that case is not handled properly anyway (exception thrown).



                            def double_chunker(lst):
                            yield [lst[0]]
                            for i in range(1, len(lst)):
                            yield [lst[i-1], lst[i]]
                            yield [lst[-1]]


                            Then, it becomes obvious what we want to do: we want to iterate over all pairs of consecutive items in a list which is a problem common enough to find various solutions to it.







                            share|improve this answer












                            share|improve this answer



                            share|improve this answer










                            answered Jan 17 at 13:22









                            JosayJosay

                            25.7k14087




                            25.7k14087





















                                3












                                $begingroup$


                                Is there any better code practices I should be employing and am not?




                                • The first thing that caught my attention is the missing tests

                                You should implement a few test cases to ensure that after changes the program does still work as intended



                                Both the unittest module or doctest are good Python modules for testing, I have used the unittest as an example



                                class PascalTriangleTest(unittest.TestCase):
                                def test_triangle_0(self):
                                self.assertEqual(
                                pascal_triangle(0),
                                [[1]]
                                )

                                def test_triangle_1(self):
                                self.assertEqual(
                                pascal_triangle(1),
                                [[1], [1, 1]]
                                )

                                def test_triangle_2(self):
                                self.assertEqual(
                                pascal_triangle(2),
                                [[1], [1, 1], [1, 2, 1]]
                                )

                                def test_triangle_3(self):
                                self.assertEqual(
                                pascal_triangle(3),
                                [[1], [1, 1], [1, 2, 1], [1, 3, 3, 1]]
                                )

                                if __name__ == '__main__':
                                unittest.main()


                                • The second one would be the missing docstrings

                                The comments below your code would be a good start to make the docstring for each function.



                                See PEP257, for docstring conventions






                                share|improve this answer









                                $endgroup$

















                                  3












                                  $begingroup$


                                  Is there any better code practices I should be employing and am not?




                                  • The first thing that caught my attention is the missing tests

                                  You should implement a few test cases to ensure that after changes the program does still work as intended



                                  Both the unittest module or doctest are good Python modules for testing, I have used the unittest as an example



                                  class PascalTriangleTest(unittest.TestCase):
                                  def test_triangle_0(self):
                                  self.assertEqual(
                                  pascal_triangle(0),
                                  [[1]]
                                  )

                                  def test_triangle_1(self):
                                  self.assertEqual(
                                  pascal_triangle(1),
                                  [[1], [1, 1]]
                                  )

                                  def test_triangle_2(self):
                                  self.assertEqual(
                                  pascal_triangle(2),
                                  [[1], [1, 1], [1, 2, 1]]
                                  )

                                  def test_triangle_3(self):
                                  self.assertEqual(
                                  pascal_triangle(3),
                                  [[1], [1, 1], [1, 2, 1], [1, 3, 3, 1]]
                                  )

                                  if __name__ == '__main__':
                                  unittest.main()


                                  • The second one would be the missing docstrings

                                  The comments below your code would be a good start to make the docstring for each function.



                                  See PEP257, for docstring conventions






                                  share|improve this answer









                                  $endgroup$















                                    3












                                    3








                                    3





                                    $begingroup$


                                    Is there any better code practices I should be employing and am not?




                                    • The first thing that caught my attention is the missing tests

                                    You should implement a few test cases to ensure that after changes the program does still work as intended



                                    Both the unittest module or doctest are good Python modules for testing, I have used the unittest as an example



                                    class PascalTriangleTest(unittest.TestCase):
                                    def test_triangle_0(self):
                                    self.assertEqual(
                                    pascal_triangle(0),
                                    [[1]]
                                    )

                                    def test_triangle_1(self):
                                    self.assertEqual(
                                    pascal_triangle(1),
                                    [[1], [1, 1]]
                                    )

                                    def test_triangle_2(self):
                                    self.assertEqual(
                                    pascal_triangle(2),
                                    [[1], [1, 1], [1, 2, 1]]
                                    )

                                    def test_triangle_3(self):
                                    self.assertEqual(
                                    pascal_triangle(3),
                                    [[1], [1, 1], [1, 2, 1], [1, 3, 3, 1]]
                                    )

                                    if __name__ == '__main__':
                                    unittest.main()


                                    • The second one would be the missing docstrings

                                    The comments below your code would be a good start to make the docstring for each function.



                                    See PEP257, for docstring conventions






                                    share|improve this answer









                                    $endgroup$




                                    Is there any better code practices I should be employing and am not?




                                    • The first thing that caught my attention is the missing tests

                                    You should implement a few test cases to ensure that after changes the program does still work as intended



                                    Both the unittest module or doctest are good Python modules for testing, I have used the unittest as an example



                                    class PascalTriangleTest(unittest.TestCase):
                                    def test_triangle_0(self):
                                    self.assertEqual(
                                    pascal_triangle(0),
                                    [[1]]
                                    )

                                    def test_triangle_1(self):
                                    self.assertEqual(
                                    pascal_triangle(1),
                                    [[1], [1, 1]]
                                    )

                                    def test_triangle_2(self):
                                    self.assertEqual(
                                    pascal_triangle(2),
                                    [[1], [1, 1], [1, 2, 1]]
                                    )

                                    def test_triangle_3(self):
                                    self.assertEqual(
                                    pascal_triangle(3),
                                    [[1], [1, 1], [1, 2, 1], [1, 3, 3, 1]]
                                    )

                                    if __name__ == '__main__':
                                    unittest.main()


                                    • The second one would be the missing docstrings

                                    The comments below your code would be a good start to make the docstring for each function.



                                    See PEP257, for docstring conventions







                                    share|improve this answer












                                    share|improve this answer



                                    share|improve this answer










                                    answered Jan 17 at 14:19









                                    LudisposedLudisposed

                                    7,80721960




                                    7,80721960





















                                        2












                                        $begingroup$


                                        Is there anything in my program that seems redundant, repetitive, or can be shortened?




                                        The 22 lines of double_chunker, chunk_adder, and pascal_next can be shortened to



                                        def pascal_next(lst):
                                        return [left + right for (left, right) in zip(lst + [0], [0] + lst)]





                                        share|improve this answer









                                        $endgroup$








                                        • 1




                                          $begingroup$
                                          Or return [sum(pair) for pair in zip(lst + [0], [0] + lst)] to make use of the built in sum
                                          $endgroup$
                                          – Ludisposed
                                          Jan 17 at 14:47











                                        • $begingroup$
                                          @Ludisposed, I deliberately chose not to do that because I regard it as a pessimisation.
                                          $endgroup$
                                          – Peter Taylor
                                          Jan 17 at 15:28










                                        • $begingroup$
                                          You could also omit the parenthesis: [left + right for left, right in zip(lst + [0], [0] + lst)].
                                          $endgroup$
                                          – Graipher
                                          Jan 17 at 15:30















                                        2












                                        $begingroup$


                                        Is there anything in my program that seems redundant, repetitive, or can be shortened?




                                        The 22 lines of double_chunker, chunk_adder, and pascal_next can be shortened to



                                        def pascal_next(lst):
                                        return [left + right for (left, right) in zip(lst + [0], [0] + lst)]





                                        share|improve this answer









                                        $endgroup$








                                        • 1




                                          $begingroup$
                                          Or return [sum(pair) for pair in zip(lst + [0], [0] + lst)] to make use of the built in sum
                                          $endgroup$
                                          – Ludisposed
                                          Jan 17 at 14:47











                                        • $begingroup$
                                          @Ludisposed, I deliberately chose not to do that because I regard it as a pessimisation.
                                          $endgroup$
                                          – Peter Taylor
                                          Jan 17 at 15:28










                                        • $begingroup$
                                          You could also omit the parenthesis: [left + right for left, right in zip(lst + [0], [0] + lst)].
                                          $endgroup$
                                          – Graipher
                                          Jan 17 at 15:30













                                        2












                                        2








                                        2





                                        $begingroup$


                                        Is there anything in my program that seems redundant, repetitive, or can be shortened?




                                        The 22 lines of double_chunker, chunk_adder, and pascal_next can be shortened to



                                        def pascal_next(lst):
                                        return [left + right for (left, right) in zip(lst + [0], [0] + lst)]





                                        share|improve this answer









                                        $endgroup$




                                        Is there anything in my program that seems redundant, repetitive, or can be shortened?




                                        The 22 lines of double_chunker, chunk_adder, and pascal_next can be shortened to



                                        def pascal_next(lst):
                                        return [left + right for (left, right) in zip(lst + [0], [0] + lst)]






                                        share|improve this answer












                                        share|improve this answer



                                        share|improve this answer










                                        answered Jan 17 at 14:44









                                        Peter TaylorPeter Taylor

                                        16.2k2860




                                        16.2k2860







                                        • 1




                                          $begingroup$
                                          Or return [sum(pair) for pair in zip(lst + [0], [0] + lst)] to make use of the built in sum
                                          $endgroup$
                                          – Ludisposed
                                          Jan 17 at 14:47











                                        • $begingroup$
                                          @Ludisposed, I deliberately chose not to do that because I regard it as a pessimisation.
                                          $endgroup$
                                          – Peter Taylor
                                          Jan 17 at 15:28










                                        • $begingroup$
                                          You could also omit the parenthesis: [left + right for left, right in zip(lst + [0], [0] + lst)].
                                          $endgroup$
                                          – Graipher
                                          Jan 17 at 15:30












                                        • 1




                                          $begingroup$
                                          Or return [sum(pair) for pair in zip(lst + [0], [0] + lst)] to make use of the built in sum
                                          $endgroup$
                                          – Ludisposed
                                          Jan 17 at 14:47











                                        • $begingroup$
                                          @Ludisposed, I deliberately chose not to do that because I regard it as a pessimisation.
                                          $endgroup$
                                          – Peter Taylor
                                          Jan 17 at 15:28










                                        • $begingroup$
                                          You could also omit the parenthesis: [left + right for left, right in zip(lst + [0], [0] + lst)].
                                          $endgroup$
                                          – Graipher
                                          Jan 17 at 15:30







                                        1




                                        1




                                        $begingroup$
                                        Or return [sum(pair) for pair in zip(lst + [0], [0] + lst)] to make use of the built in sum
                                        $endgroup$
                                        – Ludisposed
                                        Jan 17 at 14:47





                                        $begingroup$
                                        Or return [sum(pair) for pair in zip(lst + [0], [0] + lst)] to make use of the built in sum
                                        $endgroup$
                                        – Ludisposed
                                        Jan 17 at 14:47













                                        $begingroup$
                                        @Ludisposed, I deliberately chose not to do that because I regard it as a pessimisation.
                                        $endgroup$
                                        – Peter Taylor
                                        Jan 17 at 15:28




                                        $begingroup$
                                        @Ludisposed, I deliberately chose not to do that because I regard it as a pessimisation.
                                        $endgroup$
                                        – Peter Taylor
                                        Jan 17 at 15:28












                                        $begingroup$
                                        You could also omit the parenthesis: [left + right for left, right in zip(lst + [0], [0] + lst)].
                                        $endgroup$
                                        – Graipher
                                        Jan 17 at 15:30




                                        $begingroup$
                                        You could also omit the parenthesis: [left + right for left, right in zip(lst + [0], [0] + lst)].
                                        $endgroup$
                                        – Graipher
                                        Jan 17 at 15:30

















                                        draft saved

                                        draft discarded
















































                                        Thanks for contributing an answer to Code Review Stack Exchange!


                                        • Please be sure to answer the question. Provide details and share your research!

                                        But avoid


                                        • Asking for help, clarification, or responding to other answers.

                                        • Making statements based on opinion; back them up with references or personal experience.

                                        Use MathJax to format equations. MathJax reference.


                                        To learn more, see our tips on writing great answers.




                                        draft saved


                                        draft discarded














                                        StackExchange.ready(
                                        function ()
                                        StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f211677%2fpascals-triangle-generator-in-python%23new-answer', 'question_page');

                                        );

                                        Post as a guest















                                        Required, but never shown





















































                                        Required, but never shown














                                        Required, but never shown












                                        Required, but never shown







                                        Required, but never shown

































                                        Required, but never shown














                                        Required, but never shown












                                        Required, but never shown







                                        Required, but never shown






                                        Popular posts from this blog

                                        How to check contact read email or not when send email to Individual?

                                        Bahrain

                                        Postfix configuration issue with fips on centos 7; mailgun relay