All checks were successful
continuous-integration/drone/push Build is passing
The algorithm parts to clean up the four parts of the convex hull were not implemented yet. Also added draw function for completed algorithm.
390 lines
16 KiB
Python
390 lines
16 KiB
Python
#!/usr/bin/python3
|
|
|
|
"""The sweep module defines a base class sweep for various sweep algorithms. It also contains a
|
|
child-class for each individual algorithm."""
|
|
|
|
import copy
|
|
|
|
import linesegment
|
|
import point
|
|
|
|
class Algorithm():
|
|
|
|
"The Algorithm class is the base class for each individual algorithm"
|
|
|
|
def __init__(self):
|
|
self._input = None # The input values
|
|
self._result = {} # The result of the sweep
|
|
self._ready = False # If the algorithm is ready to be executed
|
|
self._running = False # If the algorithm is currently running
|
|
self._section = "start" # A string that names the current section of the algorithm
|
|
self._num_steps = {} # The number of steps that were needed to solve the problem
|
|
|
|
def start(self):
|
|
"""Start the performing the algorithm. Return True if it was successful"""
|
|
if not self._ready:
|
|
print("Error: Sweep is not yet ready to be performed")
|
|
return False
|
|
self._running = True
|
|
return True
|
|
|
|
def step(self):
|
|
"""Perform a single step of the algorithm."""
|
|
if self._section:
|
|
if self._section in self._num_steps:
|
|
self._num_steps[self._section] += 1
|
|
else:
|
|
self._num_steps[self._section] = 1
|
|
|
|
def run(self):
|
|
"""Execute the sweep. Returns the result dict."""
|
|
if not self.start():
|
|
return False
|
|
while self._running:
|
|
self.step()
|
|
return self._result
|
|
|
|
def is_running(self):
|
|
"""Return if the algorithm is running."""
|
|
return self._running
|
|
def get_result(self):
|
|
"""Return the result of the algorithm."""
|
|
return self._result
|
|
|
|
def draw(self, canvas, width, height, max_x, max_y):
|
|
"""Dummy for draw method"""
|
|
|
|
class NearestNeighborsSweep(Algorithm):
|
|
|
|
"""Calculate the pair of points inside a set, that have the minimal distance between each
|
|
other inside this set"""
|
|
|
|
def __init__(self, point_set=None):
|
|
super().__init__()
|
|
if isinstance(point_set, list):
|
|
for pnt in point_set:
|
|
if not isinstance(pnt, point.Point):
|
|
raise Exception(self, "Inputs have to be of type point.Point!")
|
|
self._input = copy.deepcopy(point_set)
|
|
self._ready = True
|
|
elif point_set is not None:
|
|
raise Exception(self, "Wrong input type for point_set")
|
|
self._sss = {} # The sweep state structure
|
|
self._es = [] # The event queue
|
|
self._result = {"success": False, \
|
|
"distance": None, \
|
|
"points": [None, None]}
|
|
|
|
def start(self):
|
|
"""Start the performing the algorithm. Return True if it was successful"""
|
|
if not super().start():
|
|
return False
|
|
|
|
self._result["success"] = False
|
|
self._sss = {"mindist": None, \
|
|
"points": []}
|
|
# Populate event structure
|
|
self._es = copy.deepcopy(self._input)
|
|
# Sort the input elements
|
|
self._num_steps["sort"] = point.sort_set(self._es)
|
|
return True
|
|
|
|
def step(self):
|
|
"""Perform a single step of the algorithm."""
|
|
if not self._running:
|
|
return
|
|
|
|
self._section = "sweep"
|
|
|
|
event = self._es.pop(0)
|
|
|
|
if self._sss["mindist"] is not None:
|
|
while len(self._sss["points"]) > 0 and \
|
|
self._sss["points"][0].get_x() <= (event.get_x() - self._sss["mindist"]):
|
|
del self._sss["points"][0]
|
|
for pnt in self._sss["points"]:
|
|
if self._sss["mindist"] is None:
|
|
self._sss["mindist"] = linesegment.LineSegment(pnt, event).get_length()
|
|
self._result["points"][0] = pnt
|
|
self._result["points"][1] = event
|
|
elif (event.get_y() - self._sss["mindist"]) < \
|
|
pnt.get_y() < (event.get_y() + self._sss["mindist"]):
|
|
distance = linesegment.LineSegment(pnt, event).get_length()
|
|
if self._sss["mindist"] > distance > 0:
|
|
self._sss["mindist"] = distance
|
|
self._result["points"][0] = pnt
|
|
self._result["points"][1] = event
|
|
super().step()
|
|
|
|
self._sss["points"].append(event)
|
|
|
|
if len(self._es) == 0:
|
|
self._result["success"] = True
|
|
self._result["distance"] = self._sss["mindist"]
|
|
self._running = False
|
|
|
|
def get_result_string(self, print_result=False):
|
|
"""Pack the result into a string."""
|
|
result_string = ""
|
|
if self._running:
|
|
result_string = "Sweep is still running."
|
|
elif not self._result["success"]:
|
|
result_string = "Sweep was not successful."
|
|
else:
|
|
result_string = "Sweep successfully completed\n"
|
|
result_string += " Summary:\n"
|
|
result_string += " Number of Inputs:" + str(len(self._input)) + "\n"
|
|
result_string += " Inputs: ["
|
|
for i, pnt in enumerate(self._input):
|
|
if i:
|
|
result_string += ","
|
|
result_string += str(pnt)
|
|
result_string += "]\n"
|
|
result_string += " Nearest Neighbors: "
|
|
result_string += str(linesegment.LineSegment(self._result["points"][0], \
|
|
self._result["points"][1]))
|
|
result_string += "\n"
|
|
result_string += " Distance: " + str(self._result["distance"]) + "\n"
|
|
result_string += " Number of steps:\n"
|
|
result_string += " Sort:" + str(self._num_steps["sort"]) +"\n"
|
|
result_string += " Sweep:" + str(self._num_steps["sweep"]) +"\n"
|
|
|
|
if print_result:
|
|
print(result_string)
|
|
return result_string
|
|
|
|
def draw(self, canvas, width, height, max_x, max_y):
|
|
"""Draw the algorithm state on a canvas"""
|
|
canvas.delete("all")
|
|
if self._running:
|
|
for pnt in self._input:
|
|
if len(self._es) > 0 and pnt == self._es[0]:
|
|
cur_x, cur_y = self._es[0].draw(canvas, width, height, max_x, max_y, "#0000FF")
|
|
canvas.create_line(cur_x, 0, cur_x, height, fill="#0000FF")
|
|
if self._sss["mindist"]:
|
|
canvas.create_line(cur_x - (self._sss["mindist"] * width / max_x), 0, \
|
|
cur_x - (self._sss["mindist"] * width / max_x), height, fill="#0000FF")
|
|
canvas.create_line(cur_x - (self._sss["mindist"] * width / max_x), \
|
|
cur_y - (self._sss["mindist"] * height / max_y), \
|
|
cur_x, cur_y - (self._sss["mindist"] * height / max_y), fill="#0000FF")
|
|
canvas.create_line(cur_x - (self._sss["mindist"] * width / max_x), \
|
|
cur_y + (self._sss["mindist"] * height / max_y), \
|
|
cur_x, cur_y + (self._sss["mindist"] * height / max_y), fill="#0000FF")
|
|
else:
|
|
canvas.create_line(0, 0, 0, max_y, fill="#0000FF")
|
|
if pnt in self._result["points"]:
|
|
pnt.draw(canvas, width, height, max_x, max_y, "#FF0000")
|
|
else:
|
|
pnt.draw(canvas, width, height, max_x, max_y)
|
|
if self._result["points"][0]:
|
|
linesegment.LineSegment( \
|
|
self._result["points"][0], self._result["points"][1] \
|
|
).draw(canvas, width, height, max_x, max_y, "#FF0000")
|
|
else:
|
|
for pnt in self._input:
|
|
if pnt in self._result["points"]:
|
|
pnt.draw(canvas, width, height, max_x, max_y, "#FF3333")
|
|
else:
|
|
pnt.draw(canvas, width, height, max_x, max_y, "#222222")
|
|
if self._result["points"][0]:
|
|
linesegment.LineSegment( \
|
|
self._result["points"][0], self._result["points"][1] \
|
|
).draw(canvas, width, height, max_x, max_y, "#FF0000")
|
|
|
|
class ConvexHullIncremental(Algorithm):
|
|
|
|
"""This algorithm calculates the convex hull for a set of points"""
|
|
|
|
def __init__(self, point_set=None):
|
|
super().__init__()
|
|
if isinstance(point_set, list):
|
|
for pnt in point_set:
|
|
if not isinstance(pnt, point.Point):
|
|
raise Exception(self, "Inputs have to be of type point.Point!")
|
|
self._input = copy.deepcopy(point_set)
|
|
self._ready = True
|
|
elif point_set is not None:
|
|
raise Exception(self, "Wrong input type for point_set")
|
|
self._sss = {} # The sweep state structure
|
|
self._es = [] # The event queue
|
|
self._result = {"success": False, \
|
|
"lt": [], \
|
|
"lb": [], \
|
|
"rt": [], \
|
|
"rb": [], \
|
|
"hull": []}
|
|
def start(self):
|
|
"""Start the performing the algorithm. Return True if it was successful"""
|
|
if not super().start():
|
|
return False
|
|
|
|
self._result["success"] = False
|
|
self._sss = {"max": None, \
|
|
"min": None, \
|
|
"lt": [], \
|
|
"lb": [], \
|
|
"rt": [], \
|
|
"rb": []}
|
|
# Populate event structure
|
|
self._es = copy.deepcopy(self._input)
|
|
# Sort the input elements
|
|
self._num_steps["sort"] = point.sort_set(self._es)
|
|
self._section = "sweep-left-to-right"
|
|
return True
|
|
def step(self):
|
|
"""Perform a single step of the algorithm."""
|
|
if not self._running:
|
|
return
|
|
|
|
if self._section == "sweep-left-to-right":
|
|
event = self._es.pop(0)
|
|
if self._sss["min"] is None or self._sss["max"] is None:
|
|
self._sss["min"] = event
|
|
self._result["lb"].append(event)
|
|
self._sss["max"] = event
|
|
self._result["lt"].append(event)
|
|
elif event.get_y() < self._sss["min"].get_y():
|
|
self._result["lb"].append(event)
|
|
self._sss["min"] = event
|
|
elif event.get_y() > self._sss["max"].get_y():
|
|
self._result["lt"].append(event)
|
|
self._sss["max"] = event
|
|
|
|
super().step()
|
|
if len(self._es) == 0:
|
|
self._sss["min"] = None
|
|
self._sss["max"] = None
|
|
self._es = copy.deepcopy(self._input)
|
|
self._num_steps["sort"] = point.sort_set(self._es)
|
|
self._section = "sweep-right-to-left"
|
|
|
|
elif self._section == "sweep-right-to-left":
|
|
event = self._es.pop(-1)
|
|
if self._sss["min"] is None or self._sss["max"] is None:
|
|
self._sss["min"] = event
|
|
self._result["rb"].append(event)
|
|
self._sss["max"] = event
|
|
self._result["rt"].append(event)
|
|
elif event.get_y() < self._sss["min"].get_y():
|
|
self._result["rb"].append(event)
|
|
self._sss["min"] = event
|
|
elif event.get_y() > self._sss["max"].get_y():
|
|
self._result["rt"].append(event)
|
|
self._sss["max"] = event
|
|
|
|
super().step()
|
|
if len(self._es) == 0:
|
|
self._es = copy.deepcopy(self._result["lt"])
|
|
self._section = "sweep-left-to-top"
|
|
|
|
elif self._section == "sweep-left-to-top":
|
|
event = self._es.pop(0)
|
|
self._sss["lt"].append(event)
|
|
start_idx = len(self._sss["lt"]) - 1
|
|
for i in range(start_idx, 0, -1):
|
|
if len(self._es) > 0:
|
|
slope_a = linesegment.LineSegment( \
|
|
self._sss["lt"][i-1], self._sss["lt"][i]).get_slope()
|
|
slope_b = linesegment.LineSegment(self._sss["lt"][i], self._es[0]).get_slope()
|
|
super().step()
|
|
if slope_a < slope_b:
|
|
del self._sss["lt"][i]
|
|
else:
|
|
break
|
|
else:
|
|
break
|
|
if len(self._es) == 0:
|
|
self._result["lt"] = copy.deepcopy(self._sss["lt"])
|
|
self._es = copy.deepcopy(self._result["lb"])
|
|
self._section = "sweep-left-to-bottom"
|
|
|
|
elif self._section == "sweep-left-to-bottom":
|
|
event = self._es.pop(0)
|
|
self._sss["lb"].append(event)
|
|
start_idx = len(self._sss["lb"]) - 1
|
|
for i in range(start_idx, 0, -1):
|
|
if len(self._es) > 0:
|
|
slope_a = linesegment.LineSegment( \
|
|
self._sss["lb"][i-1], self._sss["lb"][i]).get_slope()
|
|
slope_b = linesegment.LineSegment(self._sss["lb"][i], self._es[0]).get_slope()
|
|
super().step()
|
|
if slope_a > slope_b:
|
|
del self._sss["lb"][i]
|
|
else:
|
|
break
|
|
else:
|
|
break
|
|
if len(self._es) == 0:
|
|
self._result["lb"] = copy.deepcopy(self._sss["lb"])
|
|
self._es = copy.deepcopy(self._result["rt"])
|
|
self._section = "sweep-right-to-top"
|
|
|
|
elif self._section == "sweep-right-to-top":
|
|
event = self._es.pop(0)
|
|
self._sss["rt"].append(event)
|
|
start_idx = len(self._sss["rt"]) - 1
|
|
for i in range(start_idx, 0, -1):
|
|
if len(self._es) > 0:
|
|
slope_a = linesegment.LineSegment( \
|
|
self._sss["rt"][i-1], self._sss["rt"][i]).get_slope()
|
|
slope_b = linesegment.LineSegment(self._sss["rt"][i], self._es[0]).get_slope()
|
|
super().step()
|
|
if slope_a > slope_b:
|
|
del self._sss["rt"][i]
|
|
else:
|
|
break
|
|
else:
|
|
break
|
|
if len(self._es) == 0:
|
|
self._result["rt"] = copy.deepcopy(self._sss["rt"])
|
|
self._es = copy.deepcopy(self._result["rb"])
|
|
self._section = "sweep-right-to-bottom"
|
|
|
|
elif self._section == "sweep-right-to-bottom":
|
|
event = self._es.pop(0)
|
|
self._sss["rb"].append(event)
|
|
start_idx = len(self._sss["rb"]) - 1
|
|
for i in range(start_idx, 0, -1):
|
|
if len(self._es) > 0:
|
|
slope_a = linesegment.LineSegment( \
|
|
self._sss["rb"][i-1], self._sss["rb"][i]).get_slope()
|
|
slope_b = linesegment.LineSegment(self._sss["rb"][i], self._es[0]).get_slope()
|
|
super().step()
|
|
if slope_a < slope_b:
|
|
del self._sss["rb"][i]
|
|
else:
|
|
break
|
|
else:
|
|
break
|
|
if len(self._es) == 0:
|
|
self._result["rb"] = copy.deepcopy(self._sss["rb"])
|
|
self._section = "report-hull"
|
|
|
|
elif self._section == "report-hull":
|
|
for i in range(len(self._result["lb"]) - 1):
|
|
self._result["hull"].append(self._result["lb"][i])
|
|
for i in range(len(self._result["rb"]) - 1, 0, -1):
|
|
self._result["hull"].append(self._result["rb"][i])
|
|
for i in range(len(self._result["rt"]) - 1):
|
|
self._result["hull"].append(self._result["rt"][i])
|
|
for i in range(len(self._result["lt"]) - 1, 0, -1):
|
|
self._result["hull"].append(self._result["lt"][i])
|
|
self._result["success"] = True
|
|
self._running = False
|
|
def draw(self, canvas, width, height, max_x, max_y):
|
|
if self._running:
|
|
pass
|
|
else:
|
|
for i in range(len(self._result["hull"]) - 1):
|
|
linesegment.LineSegment( \
|
|
self._result["hull"][i], self._result["hull"][i+1]) \
|
|
.draw(canvas, width, height, max_x, max_y, "#FF0000")
|
|
linesegment.LineSegment( \
|
|
self._result["hull"][-1], self._result["hull"][0]) \
|
|
.draw(canvas, width, height, max_x, max_y, "#FF0000")
|
|
for pnt in self._input:
|
|
if pnt in self._result["hull"]:
|
|
pnt.draw(canvas, width, height, max_x, max_y, "#000000")
|
|
else:
|
|
pnt.draw(canvas, width, height, max_x, max_y, "#777777")
|