Last active
June 3, 2017 01:22
-
-
Save sthware/8700b47c9d66473aade4fbd70a76d4f0 to your computer and use it in GitHub Desktop.
Food Delivery Bot - simple delivery routing system demo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
"""Foodbot test program | |
Simple food delivery bot. Accepts a grid size and list of coordinates | |
representing dropoff locations (represented by the string 'D') | |
Plots a route to each of those locations, displaying the steps it used to | |
get there in NEWS format. | |
""" | |
import argparse, re, copy, sys, pprint | |
__author__ = "Spencer Hoffman" | |
__version__ = "1.0.0" | |
__email__ = "[email protected]" | |
__status__ = "One-off" | |
class DeliverySet: | |
def __init__(self, width: int, height: int, coordinates: list, DEBUG: bool): | |
""" Sets up delivery grid and coordinate list. | |
width -- grid width, positive integer | |
height -- grid height, positive integer | |
coordinates -- LoL of delivery coordinates in [x, y] format | |
DEBUG -- boolean flag for additional delivery information | |
""" | |
self.width = width | |
self.height = height | |
self.grid = [x[:] for x in [[0] * width] * height] | |
self.DEBUG = DEBUG | |
self.coordinates = coordinates | |
# Set up location grid with drop locations for printing. | |
for coordinate in self.coordinates: | |
x = coordinate[0] | |
y = coordinate[1] | |
# Number of deliveries at a particular location. | |
self.grid[x][y] += 1 | |
# Always have the origin in our coordinate list at least once. | |
if (0, 0) not in self.coordinates: | |
self.coordinates.insert(0, (0,0)) | |
def print_matrix(self): | |
""" Pretty prints the raw matrix """ | |
pprint.pprint(self.grid) | |
def show_route(self): | |
""" Display route to each delivery location in NEWS format. Multiple | |
deliveries at the same location will show multiple 'D' strings. | |
""" | |
prev_x = None | |
prev_y = None | |
drop_complete = False | |
end_char='' | |
for coordinate in self.coordinates: | |
x = coordinate[0] | |
y = coordinate[1] | |
x_range = [] | |
y_range = [] | |
x_steps = None | |
y_steps = None | |
if prev_x is None and prev_y is None: | |
prev_x = 0 | |
prev_y = 0 | |
continue | |
# Some locations have multiple deliveries, since we do them | |
# all at once, we can stop processing if we see the same coordinate | |
# again. | |
if self.grid[x][y] == 0: | |
continue | |
x_steps = x - prev_x | |
y_steps = y - prev_y | |
if self.DEBUG: | |
print(' ', 'Coordinates, steps: ', x, ',', y, end=' ', sep='') | |
if x_steps > 0: | |
direction = 'E' | |
x_range = range(x_steps) | |
elif x_steps < 0: | |
direction = 'W' | |
x_range = range(x, x + x_steps, -1) | |
for x_step in x_range: | |
print(direction, end=end_char) | |
if y_steps > 0: | |
direction = 'N' | |
y_range = range(y_steps) | |
elif y_steps < 0: | |
direction = 'S' | |
y_range = range(y, y + y_steps, -1) | |
for y_step in y_range: | |
print(direction, end=end_char) | |
# Routes are pre-ordered, so if a delivery location(s) is at | |
# the origin, it will print first. Otherwise it will | |
# print after taking the appropriate steps to that location. | |
# Also note that multiple deliveries at the same location | |
# will display the delivery flag repeatedly: once for each | |
# delivery. | |
while self.grid[x][y] > 0: | |
print('D', end=end_char) | |
self.grid[x][y] -= 1 | |
if self.DEBUG: | |
print() | |
# Now record the current coordinates as the previous ones so | |
# we can properly route to the next delivery location. | |
prev_x = x | |
prev_y = y | |
print() | |
def show_grid(self): | |
""" Prints the grid to match Cartesian coordinate plane orientation """ | |
output_grid = copy.deepcopy(self.grid) | |
# Typically we'd use a library for matix operations, but | |
# we're sticking to the standard library since this is a one-off. | |
# Left rotate the matrix. | |
for layer in range(self.width // 2): | |
first = layer | |
last = self.width - 1 - layer | |
for index in range(first, last): | |
upper_left_val = output_grid[first][index] | |
last_diff = self.width - 1 - index | |
output_grid[first][index] = output_grid[index][last] | |
output_grid[index][last] = output_grid[last][last_diff] | |
output_grid[last][last_diff] = output_grid[last_diff][first] | |
output_grid[last_diff][first] = upper_left_val | |
print(re.sub("[\[\]',]", " ", pprint.pformat(output_grid))) | |
def get_input(): | |
""" Parses command line arguments, and performs validation of matrix size | |
and coordinates | |
""" | |
arg_parser = argparse.ArgumentParser(description="Foodbot") | |
width = 0 | |
height = 0 | |
raw_coordinates = [] | |
raw_coordinates_str = '' | |
coordinates = [] | |
grid_size_str = '' | |
arg_parser.add_argument( | |
dest='params', | |
metavar='parameters', | |
action='store', | |
help='Grid size and moves to make. String. Ex: "4x4 (0,0) (1,1)"' | |
) | |
arg_parser.add_argument( | |
'--debug', | |
dest='DEBUG', | |
action='store_true', | |
help='print some helpful debug messages' | |
) | |
args = arg_parser.parse_args() | |
(grid_size_str, *raw_coordinates) = args.params.split(' ', 1) | |
if not raw_coordinates: | |
print("error: coordinate list must be supplied\n") | |
raise ValueError | |
# Remove spaces and starting and ending parens, as they aren't | |
# needed anymore. | |
raw_coordinates_str = raw_coordinates[0].replace(' ', '') | |
if not re.match('^ (?: \(\d+,\d+\) )+ $', raw_coordinates_str, re.X): | |
print("error: coordinates in wrong format. Use '(0, 0), (1, 1)'\n") | |
raise ValueError | |
raw_coordinates_str = raw_coordinates_str[1:] | |
raw_coordinates_str = raw_coordinates_str[:-1] | |
try: | |
(width, height) = map(int, grid_size_str.split('x')) | |
except ValueError: | |
print("error: width and height must be integers\n") | |
raise | |
if width != height: | |
print("error: only square matrices are supported\n") | |
raise ValueError | |
try: | |
for raw_coordinate in raw_coordinates_str.split(')('): | |
(x, y) = map(int, raw_coordinate.split(',')) | |
coordinates.append([x, y]) | |
except ValueError: | |
print("error: coordinates must all be integers\n") | |
raise | |
# Points are ordered for a straightforward, though not necessarily the | |
# fastest, route. | |
coordinates.sort() | |
if coordinates[-1][0] > (width - 1) or coordinates[-1][1] > (height - 1): | |
print("error: coordinate ({}, {}) out of range for grid size\n".format( | |
coordinates[-1][0], | |
coordinates[-1][1] | |
)) | |
raise ValueError | |
return (width, height, coordinates, args.DEBUG) | |
if __name__ == '__main__': | |
(width, height, coordinates, DEBUG) = get_input() | |
deliveries = DeliverySet(width, height, coordinates, DEBUG) | |
if DEBUG: | |
print("Matrix: ") | |
deliveries.print_matrix() | |
print() | |
print("Delivery grid: ") | |
deliveries.show_grid() | |
print() | |
deliveries.show_route() | |
sys.exit(0) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment