| 
						
						
							
								
							
						
						
					 | 
					 | 
					@ -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 | 
				
			
			
		
	
	
		
		
			
				
					| 
						
						
						
					 | 
					 | 
					
  |