patternpythonMinor
Python solution to Mars Rover
Viewed 0 times
solutionmarspythonrover
Problem
I'm still quite new to Python, but am loving the language (I come from a more strongly-typed language background..).
A month or so ago I found the Mars Rover Challenge and attempted to solve it.
I quite quickly managed to solve the problem, but in all honesty I can't help but feel like there is a lot of code-smells in my solution.
Could I get some help on ways to refactor functions and (if possible) add polymorphism to the classes to make it easier to extend on them?
Overall feedback would be great too!
main.py
```
#!/usr/bin/env python3
from Rover import Rover, directions
from Mars import Mars
import re
def validate_int(x, y):
"""
Validate that the two parameters are integers.
Re-used multiple times during the execution.
"""
try:
if int(x) >= 0 and int(y) >= 0:
return True
except Exception as err:
print("Only numerical elements. Try again!")
return False
def validate_rover(x, y, direction):
"""
Validates a rover to ensure the parameters are
correct datatypes(int, int, string).
It also controls ensures that the integers
are inside the Mars surface and that the
supplied direction is a correct direction.
"""
try:
if validate_int(x, y) and direction in directions:
return True
except ValueError as err:
print("Error: {}\n. Please enter two numbers followed by a char either N, E, S or W\nTry again!".format(err))
return False
print("You seem to have entered an incorrect Rover.\nPlease enter two numbers followed by a char either N, E, S or W\nTry again!\n")
return False
def validate_operations(op):
"""
Uses regex to validate that
the supplied string only contains
'M', 'R' and 'L'.
Raises a ValueError if incorrect
operation(s) have been supplied.
"""
pattern = re.compile("^[MRL]*$")
if pattern.match(op):
return True
else:
raise ValueError("Only values 'L', 'M' or 'R' a
A month or so ago I found the Mars Rover Challenge and attempted to solve it.
I quite quickly managed to solve the problem, but in all honesty I can't help but feel like there is a lot of code-smells in my solution.
Could I get some help on ways to refactor functions and (if possible) add polymorphism to the classes to make it easier to extend on them?
Overall feedback would be great too!
main.py
```
#!/usr/bin/env python3
from Rover import Rover, directions
from Mars import Mars
import re
def validate_int(x, y):
"""
Validate that the two parameters are integers.
Re-used multiple times during the execution.
"""
try:
if int(x) >= 0 and int(y) >= 0:
return True
except Exception as err:
print("Only numerical elements. Try again!")
return False
def validate_rover(x, y, direction):
"""
Validates a rover to ensure the parameters are
correct datatypes(int, int, string).
It also controls ensures that the integers
are inside the Mars surface and that the
supplied direction is a correct direction.
"""
try:
if validate_int(x, y) and direction in directions:
return True
except ValueError as err:
print("Error: {}\n. Please enter two numbers followed by a char either N, E, S or W\nTry again!".format(err))
return False
print("You seem to have entered an incorrect Rover.\nPlease enter two numbers followed by a char either N, E, S or W\nTry again!\n")
return False
def validate_operations(op):
"""
Uses regex to validate that
the supplied string only contains
'M', 'R' and 'L'.
Raises a ValueError if incorrect
operation(s) have been supplied.
"""
pattern = re.compile("^[MRL]*$")
if pattern.match(op):
return True
else:
raise ValueError("Only values 'L', 'M' or 'R' a
Solution
For once it is nice to see code which is properly commented and for the most parts is easy to read and follow. Kudos to you for that. However, there are stuff which I'm wondering on why you've done as you've done, and with possible suggestions as to how you could tackle those.
Starting from the top:
-
Validate input, not necessarily all parameters – Parameters you pass around would most likely not change by itself to something illegal. As such, it is most common to have the
In your code, you're having
-
Maybe overly fond of
-
Consider to have the
That would also alleviate the need for using the
-
Feature: Potential loop in
-
Why
-
Doing input within exception handling is not good – In general, you should avoid doing new input within exception handling as that might trigger other exceptions. The exception handling should be kept to a minimum to clear out the current error situation, and allow for normal operations to preceed (or bail out due to the severity of the exception).
-
Docstring's could be on fewer lines – It's good to have docstrings,
but you take advantage of all the 80 characters available (according to standards), as then they wouldn't take up as much space.
-
The
It could possibly be named
-
Strange logic in
It is better, but I still think I would prefer to validate the position instead of triggering an exception within
-
Avoid flag variables, especially in combination with
A much better pattern is the following:
And it would be even better if you actually used the parameter you're trying to set instead of a whimsical
-
-
Regarding
-
Why
Some refactoring
Based upon all of this I would rewrite your
```
# Create a planet to move the rovers in
x, y = input_planet_size()
planet = Planet(x, y)
while True:
x, y, direction = input_posi
Starting from the top:
-
Validate input, not necessarily all parameters – Parameters you pass around would most likely not change by itself to something illegal. As such, it is most common to have the
try...except closer to the input. In your code, you're having
try...except both in the actual input method, and also within the actual input loops. Wouldn't it be better to have a dedicated input method which did both cases?-
Maybe overly fond of
try...except? – In general we don't see a lot of try...catch in the code up for review, so it is nice to see that someone is actually thinking about error handling. It does feel like you've taken it a little long, though, and that you need to take it back a step. This is more of a general feeling though, and could be mostly personal opinion.-
Consider to have the
x, y, direction in a dedicated structure – This could possibly benefit from its own class (or structure) just to ease passing it around, and referencing it.That would also alleviate the need for using the
initials array and choice array in some parts of your code. -
Feature: Potential loop in
move(op, r) – If the rover triggers the exception you move it back to start, and start moving again (recursively). What stops it from triggering error another time? And again? And again? And ...-
Why
mars and not planet or plane? – It confuses me when I read about multiple mars'es... :-/-
Doing input within exception handling is not good – In general, you should avoid doing new input within exception handling as that might trigger other exceptions. The exception handling should be kept to a minimum to clear out the current error situation, and allow for normal operations to preceed (or bail out due to the severity of the exception).
-
Docstring's could be on fewer lines – It's good to have docstrings,
but you take advantage of all the 80 characters available (according to standards), as then they wouldn't take up as much space.
-
The
add_rover doesn't actually add a rover – Name methods after what they actually do. This method gets a new legal position and direction for a rover, and doesn't add it anywhere. It could possibly be named
get_rover_initial_position or something like that.-
Strange logic in
add_rover – Having a while True in combination with a return of if rover is not None?, and a possible exit through the check_if_exit method... My head is spinning trying to follow the logic, and it really shouldn't be that hard. Imaging something like:def get_new_rover(planet):
"""Get a valid position and direction within the planet."""
rover = None
while not Rover:
x, y, direction = get_initial_position("Please enter current rover's initial position and direction");
try:
rover = Rover(x, y, direction, planet);
exception PlanetError as err:
print(err)
return roverIt is better, but I still think I would prefer to validate the position instead of triggering an exception within
Rover. And I left out the exit handling, which could be done through raising another exceptions or at a higher level or other means. I've got to come back to this one...-
Avoid flag variables, especially in combination with
While true – You use this pattern a few times:While True:
flag = False
... do something possibly setting: flag = True
if flag:
returnA much better pattern is the following:
flag = False
While not flag:
... do something possibly setting: flag = TrueAnd it would be even better if you actually used the parameter you're trying to set instead of a whimsical
flag parameter. See rover example above.-
move_rover does a lot more than just moving the rover – It gets a new operation sequence, tries to move it, updates the planet trail/occupied list and prints a line...-
Regarding
go_end – This method by name goes to the end. It then proceeds with outputting the trail of the rovers, and boom exits. I've never liked a hidden exit in the middle of the code, and would much prefer it to be visible at a much higher level. -
Why
"exit".upper()? – As said before, I'm not entirely thrilled about the the check_if_exit() as it suddenly terminates the program. But why do you not simply write "EXIT" instead of doing it over and over again? (Most likely the compiler will remove this, but it looks a little strange)Some refactoring
Based upon all of this I would rewrite your
main method to something like the following:```
# Create a planet to move the rovers in
x, y = input_planet_size()
planet = Planet(x, y)
while True:
x, y, direction = input_posi
Code Snippets
def get_new_rover(planet):
"""Get a valid position and direction within the planet."""
rover = None
while not Rover:
x, y, direction = get_initial_position("Please enter current rover's initial position and direction");
try:
rover = Rover(x, y, direction, planet);
exception PlanetError as err:
print(err)
return roverWhile True:
flag = False
... do something possibly setting: flag = True
if flag:
returnflag = False
While not flag:
... do something possibly setting: flag = True# Create a planet to move the rovers in
x, y = input_planet_size()
planet = Planet(x, y)
while True:
x, y, direction = input_position_and_direction(planet)
# Use a negative x coordinate to trigger exit
if x < 0:
break
rover = Rover(x, y, direction)
commands = input_commands()
if commands.lower() == "exit":
break
rover.execute(commands)
planet.add_final_position(rover.get_position())
planet.print_final_positions()def input_planet_size():
"""Read and return width and height, or raise SystemExit."""
while True:
text = input("Please enter width and height (or 'exit'): ")
if text.lower() == "exit":
raise SystemExit
# Validate that the input is actually int's
try:
width, height = map(int, text.split()[:2])
except ValueError:
print("Those were not numbers (nor 'exit'). Try again")
continue
# Continue validation of numbers
if width > 0 and height > 0:
return width, height
w, h = input_planet_size()
print("Hooray, I got ({}, {})".format(w, y))Context
StackExchange Code Review Q#162988, answer score: 6
Revisions (0)
No revisions yet.