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;
}
$begingroup$
Starting in the top left corner of a 2×2 grid, and only being able to move to the right and down,
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
$endgroup$
add a comment |
$begingroup$
Starting in the top left corner of a 2×2 grid, and only being able to move to the right and down,
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
$endgroup$
add a comment |
$begingroup$
Starting in the top left corner of a 2×2 grid, and only being able to move to the right and down,
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
$endgroup$
Starting in the top left corner of a 2×2 grid, and only being able to move to the right and down,
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
python performance beginner python-3.x programming-challenge
asked 8 hours ago
emadboctoremadboctor
6841 silver badge12 bronze badges
6841 silver badge12 bronze badges
add a comment |
add a comment |
2 Answers
2
active
oldest
votes
$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.
$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
add a comment |
$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.
- The
if __name__ == '__main__':
block clearly runs two cases
- a 2x2 lattice, and
- a 20x20 lattice.
- The
pe15(rows, cols)
callscount_lattice_paths(rows, cols)
to get the result, and then prints out the result with a description of the case. - The
count_lattice_paths(rows, cols)
is the@timed
function, so the time spent printing the result report in not counted in the timing. - 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]
$endgroup$
add a comment |
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
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
$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.
$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
add a comment |
$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.
$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
add a comment |
$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.
$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.
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
add a comment |
$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
add a comment |
$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.
- The
if __name__ == '__main__':
block clearly runs two cases
- a 2x2 lattice, and
- a 20x20 lattice.
- The
pe15(rows, cols)
callscount_lattice_paths(rows, cols)
to get the result, and then prints out the result with a description of the case. - The
count_lattice_paths(rows, cols)
is the@timed
function, so the time spent printing the result report in not counted in the timing. - 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]
$endgroup$
add a comment |
$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.
- The
if __name__ == '__main__':
block clearly runs two cases
- a 2x2 lattice, and
- a 20x20 lattice.
- The
pe15(rows, cols)
callscount_lattice_paths(rows, cols)
to get the result, and then prints out the result with a description of the case. - The
count_lattice_paths(rows, cols)
is the@timed
function, so the time spent printing the result report in not counted in the timing. - 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]
$endgroup$
add a comment |
$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.
- The
if __name__ == '__main__':
block clearly runs two cases
- a 2x2 lattice, and
- a 20x20 lattice.
- The
pe15(rows, cols)
callscount_lattice_paths(rows, cols)
to get the result, and then prints out the result with a description of the case. - The
count_lattice_paths(rows, cols)
is the@timed
function, so the time spent printing the result report in not counted in the timing. - 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]
$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.
- The
if __name__ == '__main__':
block clearly runs two cases
- a 2x2 lattice, and
- a 20x20 lattice.
- The
pe15(rows, cols)
callscount_lattice_paths(rows, cols)
to get the result, and then prints out the result with a description of the case. - The
count_lattice_paths(rows, cols)
is the@timed
function, so the time spent printing the result report in not counted in the timing. - 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]
answered 4 hours ago
AJNeufeldAJNeufeld
10.8k1 gold badge8 silver badges33 bronze badges
10.8k1 gold badge8 silver badges33 bronze badges
add a comment |
add a comment |
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.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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