|
|
|
@ -8,7 +8,7 @@ class CentroidGrouping:
|
|
|
|
|
can change). This allows us to do better than just dumping the grouping |
|
|
|
|
into a dictionary with a long tuple pointing at an array. |
|
|
|
|
""" |
|
|
|
|
def __init__(self, centroid, points=[]): |
|
|
|
|
def __init__(self, centroid, points=None): |
|
|
|
|
if not isinstance(centroid, Point): |
|
|
|
|
ValueError("Centroid must be a Point.") |
|
|
|
|
|
|
|
|
@ -16,7 +16,11 @@ class CentroidGrouping:
|
|
|
|
|
ValueError("Points must be in a list.") |
|
|
|
|
|
|
|
|
|
self.__centroid = centroid |
|
|
|
|
self.__points = points |
|
|
|
|
|
|
|
|
|
if points is None: |
|
|
|
|
self.__points = [] |
|
|
|
|
else: |
|
|
|
|
self.__points = points |
|
|
|
|
|
|
|
|
|
@property |
|
|
|
|
def centroid(self): |
|
|
|
@ -32,15 +36,20 @@ class CentroidGrouping:
|
|
|
|
|
|
|
|
|
|
@param point The point. |
|
|
|
|
""" |
|
|
|
|
|
|
|
|
|
if not isinstance(point, Point): |
|
|
|
|
raise ValueError("Point must be of type Point.") |
|
|
|
|
|
|
|
|
|
self.__points.append(point) |
|
|
|
|
|
|
|
|
|
def __repr__(self): |
|
|
|
|
s = f"CENTROID: {self.__centroid}\n" |
|
|
|
|
s += f"POINTS: {self.__points}" |
|
|
|
|
|
|
|
|
|
return s |
|
|
|
|
|
|
|
|
|
def __eq__(self, other): |
|
|
|
|
return (self.centroid == other.centroid and |
|
|
|
|
self.points == other.points) |
|
|
|
|
return (self.__centroid == other.centroid and |
|
|
|
|
self.__points == other.points) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Algorithms: |
|
|
|
@ -49,72 +58,48 @@ class Algorithms:
|
|
|
|
|
geometry algorithms. |
|
|
|
|
""" |
|
|
|
|
|
|
|
|
|
# Since all algorithms rely on a set of centroids it is stored here |
|
|
|
|
# statically. |
|
|
|
|
__centroids = [] |
|
|
|
|
|
|
|
|
|
@classmethod |
|
|
|
|
def clear_centroids(cls): |
|
|
|
|
cls.__centroids = [] |
|
|
|
|
|
|
|
|
|
@classmethod |
|
|
|
|
def centroids(cls): |
|
|
|
|
return cls.__centroids |
|
|
|
|
|
|
|
|
|
@classmethod |
|
|
|
|
def set_centroids(cls, centroids): |
|
|
|
|
for c in centroids: |
|
|
|
|
if not isinstance(c, Point): |
|
|
|
|
raise ValueError("Centroids must be of type Point.") |
|
|
|
|
|
|
|
|
|
cls.__centroids.append(c) |
|
|
|
|
|
|
|
|
|
@classmethod |
|
|
|
|
def euclidean_grouping(cls, point_set): |
|
|
|
|
@staticmethod |
|
|
|
|
def euclidean_grouping(centroids, point_set): |
|
|
|
|
""" |
|
|
|
|
Given a point set that EXCLUDES the centroids specified |
|
|
|
|
it returns a map from centroid to array of points, where the array |
|
|
|
|
of points contains the points with the smallest euclidean distance |
|
|
|
|
from that point. |
|
|
|
|
|
|
|
|
|
@param cls The class calling the method. |
|
|
|
|
@param point_set The set of points from the UI. |
|
|
|
|
@param centroids The centroids to use. |
|
|
|
|
@param point_set The set of points from the UI excluding centroids. |
|
|
|
|
""" |
|
|
|
|
if not isinstance(point_set, PointSet): |
|
|
|
|
raise ValueError("Euclidean grouping can only be calculated on " + |
|
|
|
|
"PointSet types.") |
|
|
|
|
|
|
|
|
|
if not cls.__centroids: |
|
|
|
|
if not isinstance(centroids, list): |
|
|
|
|
raise ValueError("Centroids must be of type list.") |
|
|
|
|
|
|
|
|
|
if not centroids: |
|
|
|
|
raise ValueError("No centroids specified.") |
|
|
|
|
|
|
|
|
|
groups = [] |
|
|
|
|
|
|
|
|
|
for centroid in cls.__centroids: |
|
|
|
|
for centroid in centroids: |
|
|
|
|
groups.append(CentroidGrouping(centroid)) |
|
|
|
|
|
|
|
|
|
for point in point_set.points: |
|
|
|
|
nearest_distance = float("inf") |
|
|
|
|
nearest_centroid = None |
|
|
|
|
nearest_group = None |
|
|
|
|
|
|
|
|
|
for centroid in cls.__centroids: |
|
|
|
|
current_distance = Math.euclidean_distance(centroid, point) |
|
|
|
|
for current_group in groups: |
|
|
|
|
current_distance = ( |
|
|
|
|
Math.euclidean_distance(current_group.centroid, point)) |
|
|
|
|
|
|
|
|
|
if current_distance < nearest_distance: |
|
|
|
|
nearest_centroid = centroid |
|
|
|
|
nearest_group = current_group |
|
|
|
|
nearest_distance = current_distance |
|
|
|
|
|
|
|
|
|
if nearest_centroid is None: |
|
|
|
|
if nearest_group is None: |
|
|
|
|
raise ValueError("Failed to find centroid nearest " + |
|
|
|
|
f"to point {point}") |
|
|
|
|
|
|
|
|
|
# We successfully found the nearest centroid to the point |
|
|
|
|
# and we can add it to the list. |
|
|
|
|
# TODO: Can CentroidGrouping be made hashable? |
|
|
|
|
# This is relatively slow for large numbers of groups. If |
|
|
|
|
# CentroidGrouping can be made hashable then this becomes O(1). |
|
|
|
|
for group in groups: |
|
|
|
|
if nearest_centroid == group.centroid: |
|
|
|
|
group.add_point(point) |
|
|
|
|
break |
|
|
|
|
nearest_group.add_point(point) |
|
|
|
|
|
|
|
|
|
return groups |
|
|
|
|