Project Euler # 15 Lattice paths in PythonMeasure time and space requirements of different Python...

What are good ways to improve as a writer other than writing courses?

Are there any financial disadvantages to living significantly "below your means"?

What is a "Genuine Geraldo interviewee"?

How to write "upright" integrals with automatic sizing

Was the 2019 Lion King film made through motion capture?

Colleagues speaking another language and it impacts work

How would I as a DM create a smart phone-like spell/device my players could use?

How should an Administrative Asst. reply to student inquiries addressing them as "Professor" or "Doctor"?

Reusing story title as chapter title

Is refreshing multiple times a test case for web applications?

How can I re-use my password and still protect the password if it is exposed from one source?

In the movie Harry Potter and the Order or the Phoenix, why didn't Mr. Filch succeed to open the Room of Requirement if it's what he needed?

Why does Intel's Haswell chip allow multiplication to be twice as fast as addition?

Is there a loss of quality when converting RGB to HEX?

Is multiplication of real numbers uniquely defined as being distributive over addition?

Can a character who casts Shapechange and turns into a spellcaster use innate spellcasting to cast spells with a long casting time?

Why are physicists so interested in irreps if in their non-block-diagonal form they mix all components of a vector?

Team goes to lunch frequently, I do intermittent fasting but still want to socialize

Improving software when the author can see no need for improvement

Are any jet engines used in combat aircraft water cooled?

Dereferencing a pointer in a for loop initializer creates a seg fault

Is TA-ing worth the opportunity cost?

Does two puncture wounds mean venomous snake?

Should I self-publish my novella on Amazon or try my luck getting publishers?



Project Euler # 15 Lattice paths in Python


Measure time and space requirements of different Python containersoptimizing project euler #83 solutionProject Euler #15 — count possible lattice pathsProject Euler 81 (minimum path sum through a matrix)Insect combinatorics challengeFind all sub matrices of type N*M in it which are palindromicProject Euler #18 in Python: Maximum sum down a triangle of numbersProject Euler #15: counting paths through a 20 × 20 gridLattice path from Project Euler with Python solutionSolve Robot Paths using backtrackingProject Euler # 67 Maximum path sum II (Bottom up) in Python






.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty{ margin-bottom:0;
}







2












$begingroup$


Starting in the top left corner of a 2×2 grid, and only being able to move to the right and down,



2 * 2 grid



there are exactly 6 routes to the bottom right corner.
How many such routes are there through a 20×20 grid?



from time import time
from functools import lru_cache


def make_grid(grid_size):
"""Return a list of lists containing grid."""
grid = tuple((0,) * (grid_size + 1) for _ in range(grid_size + 1))
return grid


@lru_cache(None)
def count_lattice_paths(matrix, row, column):
"""Return number of all possible paths from top left to bottom right of n * n grid."""
if row == 0 or column == 0:
return 1
return count_lattice_paths(matrix, row - 1, column) + count_lattice_paths(matrix, row, column - 1)


if __name__ == '__main__':
start_time = time()
g = make_grid(20)
length = len(g) - 1
print(count_lattice_paths(g, length, length))
print(f'Time: {time() - start_time} seconds.')









share|improve this question









$endgroup$





















    2












    $begingroup$


    Starting in the top left corner of a 2×2 grid, and only being able to move to the right and down,



    2 * 2 grid



    there are exactly 6 routes to the bottom right corner.
    How many such routes are there through a 20×20 grid?



    from time import time
    from functools import lru_cache


    def make_grid(grid_size):
    """Return a list of lists containing grid."""
    grid = tuple((0,) * (grid_size + 1) for _ in range(grid_size + 1))
    return grid


    @lru_cache(None)
    def count_lattice_paths(matrix, row, column):
    """Return number of all possible paths from top left to bottom right of n * n grid."""
    if row == 0 or column == 0:
    return 1
    return count_lattice_paths(matrix, row - 1, column) + count_lattice_paths(matrix, row, column - 1)


    if __name__ == '__main__':
    start_time = time()
    g = make_grid(20)
    length = len(g) - 1
    print(count_lattice_paths(g, length, length))
    print(f'Time: {time() - start_time} seconds.')









    share|improve this question









    $endgroup$

















      2












      2








      2


      1



      $begingroup$


      Starting in the top left corner of a 2×2 grid, and only being able to move to the right and down,



      2 * 2 grid



      there are exactly 6 routes to the bottom right corner.
      How many such routes are there through a 20×20 grid?



      from time import time
      from functools import lru_cache


      def make_grid(grid_size):
      """Return a list of lists containing grid."""
      grid = tuple((0,) * (grid_size + 1) for _ in range(grid_size + 1))
      return grid


      @lru_cache(None)
      def count_lattice_paths(matrix, row, column):
      """Return number of all possible paths from top left to bottom right of n * n grid."""
      if row == 0 or column == 0:
      return 1
      return count_lattice_paths(matrix, row - 1, column) + count_lattice_paths(matrix, row, column - 1)


      if __name__ == '__main__':
      start_time = time()
      g = make_grid(20)
      length = len(g) - 1
      print(count_lattice_paths(g, length, length))
      print(f'Time: {time() - start_time} seconds.')









      share|improve this question









      $endgroup$




      Starting in the top left corner of a 2×2 grid, and only being able to move to the right and down,



      2 * 2 grid



      there are exactly 6 routes to the bottom right corner.
      How many such routes are there through a 20×20 grid?



      from time import time
      from functools import lru_cache


      def make_grid(grid_size):
      """Return a list of lists containing grid."""
      grid = tuple((0,) * (grid_size + 1) for _ in range(grid_size + 1))
      return grid


      @lru_cache(None)
      def count_lattice_paths(matrix, row, column):
      """Return number of all possible paths from top left to bottom right of n * n grid."""
      if row == 0 or column == 0:
      return 1
      return count_lattice_paths(matrix, row - 1, column) + count_lattice_paths(matrix, row, column - 1)


      if __name__ == '__main__':
      start_time = time()
      g = make_grid(20)
      length = len(g) - 1
      print(count_lattice_paths(g, length, length))
      print(f'Time: {time() - start_time} seconds.')






      python performance beginner python-3.x programming-challenge






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked 8 hours ago









      emadboctoremadboctor

      6841 silver badge12 bronze badges




      6841 silver badge12 bronze badges

























          2 Answers
          2






          active

          oldest

          votes


















          2












          $begingroup$

          I like this code.



          I like the use of the @lru_cache decorator. I appreciate seeing the elegance of the recursive solution, while knowing it has the performance of a dynamic programming one.



          I like the naming, including the fact that name complexity scales with scope. I like the clarity, including the clear handling of the recursion base case. I like the formulation of the recursion that allows you to define (0,0) to be the base case rather than anything with a 20 in it.



          I do notice that you don't actually use the matrix parameter to that function, except to pass to itself in a recursive call. I can only suspect that you had originally intended to use it to implement the dynamic programming explicitly, and didn't clean up completely when you changed plan. By the same token, make_grid can be completely removed and so can g. That incomplete cleanup is really the only criticism I would have of this code.





          I will also mention, as with many of the project Euler problems, a more mathematical lens can be employed to avoid any exhaustive searching at all. For example, to reach the corner you need some order of twenty R and twenty D. Quantifying how many of those there are is a simple instance of the Multiset Permutation calculation.



          However, it's probably faster to write this code and execute it than do that calculation, so the apparent inefficiency of running code that doesn't need to be run is probably justified.






          share|improve this answer









          $endgroup$















          • $begingroup$
            To be honest, at first I tried to cache the results manually but I failed to determine what exactly the phrasing should be, so I went for the lazy lru solution and you have a point, I should've cleaned the code up.
            $endgroup$
            – emadboctor
            7 hours ago



















          1












          $begingroup$

          Performance Counter



          When you use time.time(), you are getting the number of seconds since January 1, 1970, 00:00:00 UTC. This value is (as I'm writing this) around 1565401846.889736 ... so has about microsecond resolution.



          When you use time.perf_counter(), you are getting "a clock with the highest available resolution to measure a short duration". As I was writing this, I retrieved the value 53.149949335 ... so it has approximately nanosecond resolution.



          For profiling code, you want the highest resolution timer at your disposal ... so eschew time.time() in favour of time.perf_counter().



          Timed Decorator



          Since you are doing a lot of performance measurements in your exploration of Project Euler, you should package up the performance measurement code in a neat little package that can be easily reused. You were given a decorator for this in an answer to your "Time & Space of Python Containers" question. But here it is again, slightly modified for just doing timing:



          from time import perf_counter
          from functools import wraps

          def timed(func):
          @wraps(func)
          def wrapper(*args, **kwargs):
          start = perf_counter()
          result = func(*args, **kwargs)
          end = perf_counter()
          print(f"{func.__name__}: {end-start:.6f} seconds")
          return result
          return wrapper


          You can decorate a function with @timed, and it will report how long that function takes. You can even decorate multiple functions!



          And it moves the timing code out of your if __name__ == '__main__': block.



          Use Test Cases



          Project Euler 15 gives you the answer to a 2x2 lattice grid. You should run that test case in your code, to give yourself confidence you are getting the correct answer. Eg)



          @timed
          def count_lattice_paths(rows, cols):
          # ...

          def pe15(rows, cols):
          paths = count_lattice_paths(rows, cols)
          print(f"{rows} x {cols} = {paths} paths")

          if __name__ == '__main__':
          pe15(2, 2)
          pe15(20, 20)


          A few import points.




          1. The if __name__ == '__main__': block clearly runs two cases


            • a 2x2 lattice, and

            • a 20x20 lattice.



          2. The pe15(rows, cols) calls count_lattice_paths(rows, cols) to get the result, and then prints out the result with a description of the case.

          3. The count_lattice_paths(rows, cols) is the @timed function, so the time spent printing the result report in not counted in the timing.

          4. The count_lattice_paths() method can be generalized to work with an NxM lattice; it doesn't need to be square, even though the problem asked for the result for a square lattice and gives a square lattice test case.


          Count Lattice Paths (without a cache)



          As you noticed, a 2x2 lattice is better represented by a 3x3 grid of nodes.




          • It should be obvious there is only 1 way to reach every node along the top edge.

          • It should be obvious there is only 1 way to reach every node along the left edge.


          So, our initial array of counts would look like:



          1 1 1
          1 . .
          1 . .


          At each node (other than the top edge and left edge), the number of paths is equal to the sum of the paths to the node above it and the paths to the node to the left of it. So, there are 1+1 = 2 paths to the node at 1,1:



          1 1 1
          1 2 .
          1 . .


          Since the value at each point is defined entirely by the values above and left of it, we can directly loop over each node, and compute the required values, and finally return the value at the lower right corner of the grid. We don't even need to store the entire grid; we can compute the next row from the current row.



          @timed
          def count_lattice_paths(rows, cols):
          steps = [1] * (cols + 1)
          for _ in range(rows):
          for i in range(cols):
          steps[i+1] += steps[i]
          return steps[-1]





          share|improve this answer









          $endgroup$


















            Your Answer






            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%2f225859%2fproject-euler-15-lattice-paths-in-python%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown

























            2 Answers
            2






            active

            oldest

            votes








            2 Answers
            2






            active

            oldest

            votes









            active

            oldest

            votes






            active

            oldest

            votes









            2












            $begingroup$

            I like this code.



            I like the use of the @lru_cache decorator. I appreciate seeing the elegance of the recursive solution, while knowing it has the performance of a dynamic programming one.



            I like the naming, including the fact that name complexity scales with scope. I like the clarity, including the clear handling of the recursion base case. I like the formulation of the recursion that allows you to define (0,0) to be the base case rather than anything with a 20 in it.



            I do notice that you don't actually use the matrix parameter to that function, except to pass to itself in a recursive call. I can only suspect that you had originally intended to use it to implement the dynamic programming explicitly, and didn't clean up completely when you changed plan. By the same token, make_grid can be completely removed and so can g. That incomplete cleanup is really the only criticism I would have of this code.





            I will also mention, as with many of the project Euler problems, a more mathematical lens can be employed to avoid any exhaustive searching at all. For example, to reach the corner you need some order of twenty R and twenty D. Quantifying how many of those there are is a simple instance of the Multiset Permutation calculation.



            However, it's probably faster to write this code and execute it than do that calculation, so the apparent inefficiency of running code that doesn't need to be run is probably justified.






            share|improve this answer









            $endgroup$















            • $begingroup$
              To be honest, at first I tried to cache the results manually but I failed to determine what exactly the phrasing should be, so I went for the lazy lru solution and you have a point, I should've cleaned the code up.
              $endgroup$
              – emadboctor
              7 hours ago
















            2












            $begingroup$

            I like this code.



            I like the use of the @lru_cache decorator. I appreciate seeing the elegance of the recursive solution, while knowing it has the performance of a dynamic programming one.



            I like the naming, including the fact that name complexity scales with scope. I like the clarity, including the clear handling of the recursion base case. I like the formulation of the recursion that allows you to define (0,0) to be the base case rather than anything with a 20 in it.



            I do notice that you don't actually use the matrix parameter to that function, except to pass to itself in a recursive call. I can only suspect that you had originally intended to use it to implement the dynamic programming explicitly, and didn't clean up completely when you changed plan. By the same token, make_grid can be completely removed and so can g. That incomplete cleanup is really the only criticism I would have of this code.





            I will also mention, as with many of the project Euler problems, a more mathematical lens can be employed to avoid any exhaustive searching at all. For example, to reach the corner you need some order of twenty R and twenty D. Quantifying how many of those there are is a simple instance of the Multiset Permutation calculation.



            However, it's probably faster to write this code and execute it than do that calculation, so the apparent inefficiency of running code that doesn't need to be run is probably justified.






            share|improve this answer









            $endgroup$















            • $begingroup$
              To be honest, at first I tried to cache the results manually but I failed to determine what exactly the phrasing should be, so I went for the lazy lru solution and you have a point, I should've cleaned the code up.
              $endgroup$
              – emadboctor
              7 hours ago














            2












            2








            2





            $begingroup$

            I like this code.



            I like the use of the @lru_cache decorator. I appreciate seeing the elegance of the recursive solution, while knowing it has the performance of a dynamic programming one.



            I like the naming, including the fact that name complexity scales with scope. I like the clarity, including the clear handling of the recursion base case. I like the formulation of the recursion that allows you to define (0,0) to be the base case rather than anything with a 20 in it.



            I do notice that you don't actually use the matrix parameter to that function, except to pass to itself in a recursive call. I can only suspect that you had originally intended to use it to implement the dynamic programming explicitly, and didn't clean up completely when you changed plan. By the same token, make_grid can be completely removed and so can g. That incomplete cleanup is really the only criticism I would have of this code.





            I will also mention, as with many of the project Euler problems, a more mathematical lens can be employed to avoid any exhaustive searching at all. For example, to reach the corner you need some order of twenty R and twenty D. Quantifying how many of those there are is a simple instance of the Multiset Permutation calculation.



            However, it's probably faster to write this code and execute it than do that calculation, so the apparent inefficiency of running code that doesn't need to be run is probably justified.






            share|improve this answer









            $endgroup$



            I like this code.



            I like the use of the @lru_cache decorator. I appreciate seeing the elegance of the recursive solution, while knowing it has the performance of a dynamic programming one.



            I like the naming, including the fact that name complexity scales with scope. I like the clarity, including the clear handling of the recursion base case. I like the formulation of the recursion that allows you to define (0,0) to be the base case rather than anything with a 20 in it.



            I do notice that you don't actually use the matrix parameter to that function, except to pass to itself in a recursive call. I can only suspect that you had originally intended to use it to implement the dynamic programming explicitly, and didn't clean up completely when you changed plan. By the same token, make_grid can be completely removed and so can g. That incomplete cleanup is really the only criticism I would have of this code.





            I will also mention, as with many of the project Euler problems, a more mathematical lens can be employed to avoid any exhaustive searching at all. For example, to reach the corner you need some order of twenty R and twenty D. Quantifying how many of those there are is a simple instance of the Multiset Permutation calculation.



            However, it's probably faster to write this code and execute it than do that calculation, so the apparent inefficiency of running code that doesn't need to be run is probably justified.







            share|improve this answer












            share|improve this answer



            share|improve this answer










            answered 8 hours ago









            JosiahJosiah

            5,0041 gold badge10 silver badges33 bronze badges




            5,0041 gold badge10 silver badges33 bronze badges















            • $begingroup$
              To be honest, at first I tried to cache the results manually but I failed to determine what exactly the phrasing should be, so I went for the lazy lru solution and you have a point, I should've cleaned the code up.
              $endgroup$
              – emadboctor
              7 hours ago


















            • $begingroup$
              To be honest, at first I tried to cache the results manually but I failed to determine what exactly the phrasing should be, so I went for the lazy lru solution and you have a point, I should've cleaned the code up.
              $endgroup$
              – emadboctor
              7 hours ago
















            $begingroup$
            To be honest, at first I tried to cache the results manually but I failed to determine what exactly the phrasing should be, so I went for the lazy lru solution and you have a point, I should've cleaned the code up.
            $endgroup$
            – emadboctor
            7 hours ago




            $begingroup$
            To be honest, at first I tried to cache the results manually but I failed to determine what exactly the phrasing should be, so I went for the lazy lru solution and you have a point, I should've cleaned the code up.
            $endgroup$
            – emadboctor
            7 hours ago













            1












            $begingroup$

            Performance Counter



            When you use time.time(), you are getting the number of seconds since January 1, 1970, 00:00:00 UTC. This value is (as I'm writing this) around 1565401846.889736 ... so has about microsecond resolution.



            When you use time.perf_counter(), you are getting "a clock with the highest available resolution to measure a short duration". As I was writing this, I retrieved the value 53.149949335 ... so it has approximately nanosecond resolution.



            For profiling code, you want the highest resolution timer at your disposal ... so eschew time.time() in favour of time.perf_counter().



            Timed Decorator



            Since you are doing a lot of performance measurements in your exploration of Project Euler, you should package up the performance measurement code in a neat little package that can be easily reused. You were given a decorator for this in an answer to your "Time & Space of Python Containers" question. But here it is again, slightly modified for just doing timing:



            from time import perf_counter
            from functools import wraps

            def timed(func):
            @wraps(func)
            def wrapper(*args, **kwargs):
            start = perf_counter()
            result = func(*args, **kwargs)
            end = perf_counter()
            print(f"{func.__name__}: {end-start:.6f} seconds")
            return result
            return wrapper


            You can decorate a function with @timed, and it will report how long that function takes. You can even decorate multiple functions!



            And it moves the timing code out of your if __name__ == '__main__': block.



            Use Test Cases



            Project Euler 15 gives you the answer to a 2x2 lattice grid. You should run that test case in your code, to give yourself confidence you are getting the correct answer. Eg)



            @timed
            def count_lattice_paths(rows, cols):
            # ...

            def pe15(rows, cols):
            paths = count_lattice_paths(rows, cols)
            print(f"{rows} x {cols} = {paths} paths")

            if __name__ == '__main__':
            pe15(2, 2)
            pe15(20, 20)


            A few import points.




            1. The if __name__ == '__main__': block clearly runs two cases


              • a 2x2 lattice, and

              • a 20x20 lattice.



            2. The pe15(rows, cols) calls count_lattice_paths(rows, cols) to get the result, and then prints out the result with a description of the case.

            3. The count_lattice_paths(rows, cols) is the @timed function, so the time spent printing the result report in not counted in the timing.

            4. The count_lattice_paths() method can be generalized to work with an NxM lattice; it doesn't need to be square, even though the problem asked for the result for a square lattice and gives a square lattice test case.


            Count Lattice Paths (without a cache)



            As you noticed, a 2x2 lattice is better represented by a 3x3 grid of nodes.




            • It should be obvious there is only 1 way to reach every node along the top edge.

            • It should be obvious there is only 1 way to reach every node along the left edge.


            So, our initial array of counts would look like:



            1 1 1
            1 . .
            1 . .


            At each node (other than the top edge and left edge), the number of paths is equal to the sum of the paths to the node above it and the paths to the node to the left of it. So, there are 1+1 = 2 paths to the node at 1,1:



            1 1 1
            1 2 .
            1 . .


            Since the value at each point is defined entirely by the values above and left of it, we can directly loop over each node, and compute the required values, and finally return the value at the lower right corner of the grid. We don't even need to store the entire grid; we can compute the next row from the current row.



            @timed
            def count_lattice_paths(rows, cols):
            steps = [1] * (cols + 1)
            for _ in range(rows):
            for i in range(cols):
            steps[i+1] += steps[i]
            return steps[-1]





            share|improve this answer









            $endgroup$




















              1












              $begingroup$

              Performance Counter



              When you use time.time(), you are getting the number of seconds since January 1, 1970, 00:00:00 UTC. This value is (as I'm writing this) around 1565401846.889736 ... so has about microsecond resolution.



              When you use time.perf_counter(), you are getting "a clock with the highest available resolution to measure a short duration". As I was writing this, I retrieved the value 53.149949335 ... so it has approximately nanosecond resolution.



              For profiling code, you want the highest resolution timer at your disposal ... so eschew time.time() in favour of time.perf_counter().



              Timed Decorator



              Since you are doing a lot of performance measurements in your exploration of Project Euler, you should package up the performance measurement code in a neat little package that can be easily reused. You were given a decorator for this in an answer to your "Time & Space of Python Containers" question. But here it is again, slightly modified for just doing timing:



              from time import perf_counter
              from functools import wraps

              def timed(func):
              @wraps(func)
              def wrapper(*args, **kwargs):
              start = perf_counter()
              result = func(*args, **kwargs)
              end = perf_counter()
              print(f"{func.__name__}: {end-start:.6f} seconds")
              return result
              return wrapper


              You can decorate a function with @timed, and it will report how long that function takes. You can even decorate multiple functions!



              And it moves the timing code out of your if __name__ == '__main__': block.



              Use Test Cases



              Project Euler 15 gives you the answer to a 2x2 lattice grid. You should run that test case in your code, to give yourself confidence you are getting the correct answer. Eg)



              @timed
              def count_lattice_paths(rows, cols):
              # ...

              def pe15(rows, cols):
              paths = count_lattice_paths(rows, cols)
              print(f"{rows} x {cols} = {paths} paths")

              if __name__ == '__main__':
              pe15(2, 2)
              pe15(20, 20)


              A few import points.




              1. The if __name__ == '__main__': block clearly runs two cases


                • a 2x2 lattice, and

                • a 20x20 lattice.



              2. The pe15(rows, cols) calls count_lattice_paths(rows, cols) to get the result, and then prints out the result with a description of the case.

              3. The count_lattice_paths(rows, cols) is the @timed function, so the time spent printing the result report in not counted in the timing.

              4. The count_lattice_paths() method can be generalized to work with an NxM lattice; it doesn't need to be square, even though the problem asked for the result for a square lattice and gives a square lattice test case.


              Count Lattice Paths (without a cache)



              As you noticed, a 2x2 lattice is better represented by a 3x3 grid of nodes.




              • It should be obvious there is only 1 way to reach every node along the top edge.

              • It should be obvious there is only 1 way to reach every node along the left edge.


              So, our initial array of counts would look like:



              1 1 1
              1 . .
              1 . .


              At each node (other than the top edge and left edge), the number of paths is equal to the sum of the paths to the node above it and the paths to the node to the left of it. So, there are 1+1 = 2 paths to the node at 1,1:



              1 1 1
              1 2 .
              1 . .


              Since the value at each point is defined entirely by the values above and left of it, we can directly loop over each node, and compute the required values, and finally return the value at the lower right corner of the grid. We don't even need to store the entire grid; we can compute the next row from the current row.



              @timed
              def count_lattice_paths(rows, cols):
              steps = [1] * (cols + 1)
              for _ in range(rows):
              for i in range(cols):
              steps[i+1] += steps[i]
              return steps[-1]





              share|improve this answer









              $endgroup$


















                1












                1








                1





                $begingroup$

                Performance Counter



                When you use time.time(), you are getting the number of seconds since January 1, 1970, 00:00:00 UTC. This value is (as I'm writing this) around 1565401846.889736 ... so has about microsecond resolution.



                When you use time.perf_counter(), you are getting "a clock with the highest available resolution to measure a short duration". As I was writing this, I retrieved the value 53.149949335 ... so it has approximately nanosecond resolution.



                For profiling code, you want the highest resolution timer at your disposal ... so eschew time.time() in favour of time.perf_counter().



                Timed Decorator



                Since you are doing a lot of performance measurements in your exploration of Project Euler, you should package up the performance measurement code in a neat little package that can be easily reused. You were given a decorator for this in an answer to your "Time & Space of Python Containers" question. But here it is again, slightly modified for just doing timing:



                from time import perf_counter
                from functools import wraps

                def timed(func):
                @wraps(func)
                def wrapper(*args, **kwargs):
                start = perf_counter()
                result = func(*args, **kwargs)
                end = perf_counter()
                print(f"{func.__name__}: {end-start:.6f} seconds")
                return result
                return wrapper


                You can decorate a function with @timed, and it will report how long that function takes. You can even decorate multiple functions!



                And it moves the timing code out of your if __name__ == '__main__': block.



                Use Test Cases



                Project Euler 15 gives you the answer to a 2x2 lattice grid. You should run that test case in your code, to give yourself confidence you are getting the correct answer. Eg)



                @timed
                def count_lattice_paths(rows, cols):
                # ...

                def pe15(rows, cols):
                paths = count_lattice_paths(rows, cols)
                print(f"{rows} x {cols} = {paths} paths")

                if __name__ == '__main__':
                pe15(2, 2)
                pe15(20, 20)


                A few import points.




                1. The if __name__ == '__main__': block clearly runs two cases


                  • a 2x2 lattice, and

                  • a 20x20 lattice.



                2. The pe15(rows, cols) calls count_lattice_paths(rows, cols) to get the result, and then prints out the result with a description of the case.

                3. The count_lattice_paths(rows, cols) is the @timed function, so the time spent printing the result report in not counted in the timing.

                4. The count_lattice_paths() method can be generalized to work with an NxM lattice; it doesn't need to be square, even though the problem asked for the result for a square lattice and gives a square lattice test case.


                Count Lattice Paths (without a cache)



                As you noticed, a 2x2 lattice is better represented by a 3x3 grid of nodes.




                • It should be obvious there is only 1 way to reach every node along the top edge.

                • It should be obvious there is only 1 way to reach every node along the left edge.


                So, our initial array of counts would look like:



                1 1 1
                1 . .
                1 . .


                At each node (other than the top edge and left edge), the number of paths is equal to the sum of the paths to the node above it and the paths to the node to the left of it. So, there are 1+1 = 2 paths to the node at 1,1:



                1 1 1
                1 2 .
                1 . .


                Since the value at each point is defined entirely by the values above and left of it, we can directly loop over each node, and compute the required values, and finally return the value at the lower right corner of the grid. We don't even need to store the entire grid; we can compute the next row from the current row.



                @timed
                def count_lattice_paths(rows, cols):
                steps = [1] * (cols + 1)
                for _ in range(rows):
                for i in range(cols):
                steps[i+1] += steps[i]
                return steps[-1]





                share|improve this answer









                $endgroup$



                Performance Counter



                When you use time.time(), you are getting the number of seconds since January 1, 1970, 00:00:00 UTC. This value is (as I'm writing this) around 1565401846.889736 ... so has about microsecond resolution.



                When you use time.perf_counter(), you are getting "a clock with the highest available resolution to measure a short duration". As I was writing this, I retrieved the value 53.149949335 ... so it has approximately nanosecond resolution.



                For profiling code, you want the highest resolution timer at your disposal ... so eschew time.time() in favour of time.perf_counter().



                Timed Decorator



                Since you are doing a lot of performance measurements in your exploration of Project Euler, you should package up the performance measurement code in a neat little package that can be easily reused. You were given a decorator for this in an answer to your "Time & Space of Python Containers" question. But here it is again, slightly modified for just doing timing:



                from time import perf_counter
                from functools import wraps

                def timed(func):
                @wraps(func)
                def wrapper(*args, **kwargs):
                start = perf_counter()
                result = func(*args, **kwargs)
                end = perf_counter()
                print(f"{func.__name__}: {end-start:.6f} seconds")
                return result
                return wrapper


                You can decorate a function with @timed, and it will report how long that function takes. You can even decorate multiple functions!



                And it moves the timing code out of your if __name__ == '__main__': block.



                Use Test Cases



                Project Euler 15 gives you the answer to a 2x2 lattice grid. You should run that test case in your code, to give yourself confidence you are getting the correct answer. Eg)



                @timed
                def count_lattice_paths(rows, cols):
                # ...

                def pe15(rows, cols):
                paths = count_lattice_paths(rows, cols)
                print(f"{rows} x {cols} = {paths} paths")

                if __name__ == '__main__':
                pe15(2, 2)
                pe15(20, 20)


                A few import points.




                1. The if __name__ == '__main__': block clearly runs two cases


                  • a 2x2 lattice, and

                  • a 20x20 lattice.



                2. The pe15(rows, cols) calls count_lattice_paths(rows, cols) to get the result, and then prints out the result with a description of the case.

                3. The count_lattice_paths(rows, cols) is the @timed function, so the time spent printing the result report in not counted in the timing.

                4. The count_lattice_paths() method can be generalized to work with an NxM lattice; it doesn't need to be square, even though the problem asked for the result for a square lattice and gives a square lattice test case.


                Count Lattice Paths (without a cache)



                As you noticed, a 2x2 lattice is better represented by a 3x3 grid of nodes.




                • It should be obvious there is only 1 way to reach every node along the top edge.

                • It should be obvious there is only 1 way to reach every node along the left edge.


                So, our initial array of counts would look like:



                1 1 1
                1 . .
                1 . .


                At each node (other than the top edge and left edge), the number of paths is equal to the sum of the paths to the node above it and the paths to the node to the left of it. So, there are 1+1 = 2 paths to the node at 1,1:



                1 1 1
                1 2 .
                1 . .


                Since the value at each point is defined entirely by the values above and left of it, we can directly loop over each node, and compute the required values, and finally return the value at the lower right corner of the grid. We don't even need to store the entire grid; we can compute the next row from the current row.



                @timed
                def count_lattice_paths(rows, cols):
                steps = [1] * (cols + 1)
                for _ in range(rows):
                for i in range(cols):
                steps[i+1] += steps[i]
                return steps[-1]






                share|improve this answer












                share|improve this answer



                share|improve this answer










                answered 4 hours ago









                AJNeufeldAJNeufeld

                10.8k1 gold badge8 silver badges33 bronze badges




                10.8k1 gold badge8 silver badges33 bronze badges

































                    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%2f225859%2fproject-euler-15-lattice-paths-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

                    Taj Mahal Inhaltsverzeichnis Aufbau | Geschichte | 350-Jahr-Feier | Heutige Bedeutung | Siehe auch |...

                    Baia Sprie Cuprins Etimologie | Istorie | Demografie | Politică și administrație | Arii naturale...

                    Nicolae Petrescu-Găină Cuprins Biografie | Opera | In memoriam | Varia | Controverse, incertitudini...