1 | """ |
||
2 | Project Euler Problem 15: Lattice Paths |
||
3 | ======================================= |
||
4 | |||
5 | .. module:: solutions.problem15 |
||
6 | :synopsis: My solution to problem #15. |
||
7 | |||
8 | The source code for this problem can be |
||
9 | `found here <https://bitbucket.org/nekedome/project-euler/src/master/solutions/problem15.py>`_. |
||
10 | |||
11 | Problem Statement |
||
12 | ################# |
||
13 | |||
14 | Starting in the top left corner of a :math:`2 \\times 2` grid, and only being able to move to the right and down, there |
||
0 ignored issues
–
show
|
|||
15 | are exactly :math:`6` routes to the bottom right corner. |
||
16 | |||
17 | .. image:: ../images/p015.gif |
||
18 | :align: center |
||
19 | |||
20 | How many such routes are there through a :math:`20 \\times 20` grid? |
||
21 | |||
22 | Solution Discussion |
||
23 | ################### |
||
24 | |||
25 | Use a dynamic programming algorithm to count the number of paths through a search tree. |
||
26 | |||
27 | Solution Implementation |
||
28 | ####################### |
||
29 | |||
30 | .. literalinclude:: ../../solutions/problem15.py |
||
31 | :language: python |
||
32 | :lines: 35- |
||
33 | """ |
||
34 | |||
35 | from typing import Dict, List, Optional, Tuple |
||
36 | |||
37 | |||
38 | def search(length: int, moves: Optional[List[int]] = None, solutions: Optional[Dict[Tuple[int, int], int]] = None)\ |
||
0 ignored issues
–
show
|
|||
39 | -> int: |
||
40 | """ Recursive tree search, branching on possible choices (left or down) |
||
41 | |||
42 | Dynamic programming (solutions) is used to cache previous, partial, answers avoiding re-computation. |
||
0 ignored issues
–
show
|
|||
43 | |||
44 | :param length: the length of each axis in the grid |
||
45 | :param moves: the moves made (down and right) at this point of the search |
||
46 | :param solutions: partial solutions cache for the avoidance of redundant computations |
||
47 | :return: the number of search paths through a :math:`length \\times length` grid |
||
48 | """ |
||
49 | |||
50 | if moves is None: |
||
51 | moves = [0, 0] # initialise moves to represent none have been made |
||
52 | |||
53 | assert moves[0] <= length, "you cannot move past the edge" |
||
54 | assert moves[1] <= length, "you cannot move past the edge" |
||
55 | |||
56 | if solutions is None: |
||
57 | solutions = {} # initialise solutions to the empty cache |
||
58 | |||
59 | if moves[0] == length: |
||
0 ignored issues
–
show
|
|||
60 | return 1 # base case: no further branching possible |
||
61 | elif moves[1] == length: |
||
62 | return 1 # base case: no further branching possible |
||
63 | elif tuple(moves) in solutions: |
||
64 | return solutions[tuple(moves)] # returned cached answer |
||
65 | else: |
||
66 | # Branch down each possible path via recursion |
||
67 | answer0 = search(length, [moves[0] + 1, moves[1]], solutions) |
||
68 | solutions[(moves[0] + 1, moves[1])] = answer0 |
||
69 | answer1 = search(length, [moves[0], moves[1] + 1], solutions) |
||
70 | solutions[(moves[0], moves[1] + 1)] = answer1 |
||
71 | return answer0 + answer1 |
||
72 | |||
73 | |||
74 | def solve(): |
||
75 | """ Compute the answer to Project Euler's problem #15 """ |
||
76 | target = 20 |
||
77 | answer = search(target) |
||
78 | return answer |
||
79 | |||
80 | |||
81 | expected_answer = 137846528820 |
||
0 ignored issues
–
show
The name
expected_answer does not conform to the constant naming conventions ((([A-Z_][A-Z0-9_]*)|(__.*__))$ ).
This check looks for invalid names for a range of different identifiers. You can set regular expressions to which the identifiers must conform if the defaults do not match your requirements. If your project includes a Pylint configuration file, the settings contained in that file take precedence. To find out more about Pylint, please refer to their site. ![]() |
|||
82 |
This check looks for lines that are too long. You can specify the maximum line length.