Class: KMeans::Node

Inherits:
Object show all
Defined in:
lib/kmeans/node.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*position) ⇒ Node

Creates a node based only on the position. The dimension cardinality is enforced on the class level (I.e., all nodes must have the same number of position.)



97
98
99
100
# File 'lib/kmeans/node.rb', line 97

def initialize(*position)
  @position = position
  Node.add_node(self)
end

Instance Attribute Details

#centroidObject

Records which centroid the node is assigned to



103
104
105
# File 'lib/kmeans/node.rb', line 103

def centroid
  @centroid
end

#positionObject (readonly)

The position on every dimension



92
93
94
# File 'lib/kmeans/node.rb', line 92

def position
  @position
end

Class Method Details

.add_node(node) ⇒ Object

Adds a node, clears the cache, asserts that the parameter is a node.

Raises:

  • (ArgumentError)


67
68
69
70
71
72
73
74
75
# File 'lib/kmeans/node.rb', line 67

def add_node(node)
  node = new(*node) unless node.is_a?(Node)
  self.dimension_size = node.position.size if self.nodes.empty?
  raise ArgumentError, "Node does not have the right number of positions" unless
    node.position.size == self.dimension_size
  self.nodes << node
  @@boundaries = nil
  node
end

.add_nodes(*nodes) ⇒ Object

Instantiates the node object and adds them to the list. Example: Node.add_nodes [1,2,3], [4,5,3], [2,1,3] Node.add_nodes *node_list



60
61
62
63
64
# File 'lib/kmeans/node.rb', line 60

def add_nodes(*nodes)
  nodes.each do |node|
    node = new(*node)
  end
end

.clear_nodes!Object

Clears out all nodes. Don’t do this while working on a specific problem, but in-between problems. This nodes-on-the-Node stuff needs to be adjusted when I have some time.



8
9
10
# File 'lib/kmeans/node.rb', line 8

def clear_nodes!
  self.nodes.clear
end

.cluster_to(centroids, scaling = nil) ⇒ Object

Returns the number of re-assignments made



13
14
15
16
17
18
19
20
21
22
23
# File 'lib/kmeans/node.rb', line 13

def cluster_to(centroids, scaling=nil)
  n = 0
  self.nodes.each do |node|
    position = centroids.map { |centroid| node.distance(centroid, scaling) }.min_position
    target_centroid = centroids[position]
    next if target_centroid == node.centroid
    node.move_to(target_centroid)
    n += 1
  end
  n
end

.dimension_sizeObject



49
50
51
# File 'lib/kmeans/node.rb', line 49

def dimension_size
  @@dimension_size
end

.dimension_size=(val) ⇒ Object



53
54
55
# File 'lib/kmeans/node.rb', line 53

def dimension_size=(val)
  @@dimension_size = val
end

.find_boundariesObject Also known as: boundaries

Gets the max and min on every dimension for every node



26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/kmeans/node.rb', line 26

def find_boundaries
  return nil if self.nodes.empty?
  return @@boundaries if @@boundaries
  # Building this long-hand for good reason...
  dimensional_array = []
  self.dimension_size.times {dimensional_array << [nil, nil]}
  @@boundaries = self.nodes.inject(dimensional_array) do |list, node|
    node.position.each_with_index do |dimension, i|
      list[i][0] ||= dimension
      list[i][1] ||= dimension
      list[i][0] = dimension if dimension < list[i][0]
      list[i][1] = dimension if dimension > list[i][1]
    end
    list
  end
end

.nodesObject

A list of nodes initialized



45
46
47
# File 'lib/kmeans/node.rb', line 45

def nodes
  @@nodes ||= []
end

.random_centroidObject

A centroid that fits between the boundaries on each dimension. The boundaries for a 3-dimensional model might look like:

[1,5], [0,10], [1,100]

This means that the first dimension can be between 1 and 5, the second between 0 and 10, and the third between 1 and 100.



82
83
84
85
86
87
# File 'lib/kmeans/node.rb', line 82

def random_centroid
  position = (0...self.dimension_size).inject([]) do |list, i|
    list << rand_between(self.boundaries[i].first, self.boundaries[i].last)
  end
  Centroid.new(position)
end

Instance Method Details

#distance(centroid = nil, scaling = nil) ⇒ Object



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
# File 'lib/kmeans/node.rb', line 105

def distance(centroid=nil, scaling=nil)
  centroid ||= self.centroid
  op = centroid.position
  map = self.position.map_with_index do |e, i|
    if scaling
      (
        (op[i] * scaling[i]) - 
        (e * scaling[i])
      ) ** 2
    else
      (op[i] - e) ** 2
    end
  end
  Math.sqrt(map.sum)
end

#move_to(centroid) ⇒ Object



121
122
123
124
# File 'lib/kmeans/node.rb', line 121

def move_to(centroid)
  self.centroid.remove_node(self) if self.centroid
  centroid.add_node(self)
end