Browse Source

Default arguments don't do what you expect...

tb-init-ui-render
Taylor Bockman 5 years ago
parent
commit
24667348fd
  1. 75
      clusterview/algorithms.py
  2. 30
      tests/test_algorithms.py

75
clusterview/algorithms.py

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

30
tests/test_algorithms.py

@ -5,19 +5,9 @@ from clusterview.colors import Color
from clusterview.points import Point, PointSet from clusterview.points import Point, PointSet
@pytest.fixture(autouse=True, scope="function")
def teardown():
"""
Teardown function for after each test. The current pytest best practice
is to run a setup routine, yield, and then run your teardown routine.
"""
yield
Algorithms.clear_centroids()
def test_empty_centroids(): def test_empty_centroids():
with pytest.raises(ValueError): with pytest.raises(ValueError):
Algorithms.euclidean_grouping(None) Algorithms.euclidean_grouping([], None)
def test_wrong_point_set(): def test_wrong_point_set():
@ -27,13 +17,22 @@ def test_wrong_point_set():
centroids = [centroid_g1, centroid_g2, centroid_g3] centroids = [centroid_g1, centroid_g2, centroid_g3]
Algorithms.set_centroids(centroids) with pytest.raises(ValueError):
Algorithms.euclidean_grouping(centroids, None)
def test_empty_point_set():
centroid_g1 = Point(101, 81, Color.ORANGE, 8, 800, 600)
centroid_g2 = Point(357, 222, Color.RED, 8, 800, 600)
centroid_g3 = Point(728, 47, Color.PURPLE, 8, 800, 600)
centroids = [centroid_g1, centroid_g2, centroid_g3]
with pytest.raises(ValueError): with pytest.raises(ValueError):
Algorithms.euclidean_grouping(None) Algorithms.euclidean_grouping(centroids, [])
def test_euclidean_distance(): def test_euclidean_grouping():
centroid_g1 = Point(101, 81, Color.ORANGE, 8, 800, 600) centroid_g1 = Point(101, 81, Color.ORANGE, 8, 800, 600)
centroid_g2 = Point(357, 222, Color.RED, 8, 800, 600) centroid_g2 = Point(357, 222, Color.RED, 8, 800, 600)
centroid_g3 = Point(728, 47, Color.PURPLE, 8, 800, 600) centroid_g3 = Point(728, 47, Color.PURPLE, 8, 800, 600)
@ -77,8 +76,7 @@ def test_euclidean_distance():
expected = [centroid_grouping_1, centroid_grouping_2, centroid_grouping_3] expected = [centroid_grouping_1, centroid_grouping_2, centroid_grouping_3]
Algorithms.set_centroids(centroids) actual = Algorithms.euclidean_grouping(centroids, point_set)
actual = Algorithms.euclidean_grouping(point_set)
assert len(actual) == len(expected) assert len(actual) == len(expected)

Loading…
Cancel
Save