Skip to content

Instantly share code, notes, and snippets.

@marcosgz
Last active March 17, 2021 00:39
Show Gist options
  • Save marcosgz/0d19ddf71b60d19ca1c954f2200c3e62 to your computer and use it in GitHub Desktop.
Save marcosgz/0d19ddf71b60d19ca1c954f2200c3e62 to your computer and use it in GitHub Desktop.
K-NN algorithm using RGB samples
#!/usr/bin/env ruby
# It's ODM (Object Document Mapper) to the RGB data
class RGB
# @overload initialize(r, g, b, value)
# @param r [Integer] Value for R
# @param g [Integer] Value for G
# @param b [Integer] Value for B
# @param value [Object] describe value param
# @overload initialize(r, g, b)
# @param r [Integer] Value for R
# @param g [Integer] Value for G
# @param b [Integer] Value for B
def initialize(*args)
@r, @g, @b, @value = args
end
# Calculate euclidian distance between two instances of RGB
# @param a [RBG] An instance of RGB
# @param b [RBG] An instance of RGB
# @return [Float]
def self.distance(a, b)
power = %i[r g b].map { |attribute| (a[attribute] - b[attribute]) ** 2 }.reduce(&:+)
Math.sqrt(power)
end
# @param variable_name [Symbol, String] The name of the instance variable
# @raise [KeyError] when instance variable is not defined
def [](variable_name)
var_name = :"@#{variable_name}"
raise KeyError unless instance_variable_defined?(var_name)
instance_variable_get(var_name)
end
# Calculate euclidian distance to the given RGB object
# @param rgb[RGB] An instance of RGB
# @raise [ArgumentError] When the given rgb is not an instance of RGB
def distance(rgb)
raise ArgumentError unless rgb.is_a?(RGB)
RGB.distance(self, rgb)
end
def inspect
"#<RGB #{ %i[r g b value].map { |k| self[k] }.compact.join(' ') }>"
end
end
# Initialize a collection of RGBs
samples = [
[1, 10, 200, 1],
[2, 20, 230, 1],
[6, 25, 150, 1],
[7, 45, 100, 1],
[10, 50, 125, 1],
[3, 24, 111, 1],
[100, 4, 10, 2],
[250, 7, 50, 2],
[243, 5, 68, 2],
[210, 2, 90, 2],
[200, 1, 95, 2],
[215, 0, 68, 2],
[56, 200, 1, 3],
[79, 234, 3, 3],
[80, 210, 8, 3],
[95, 200, 10, 3],
[80, 210, 4, 3],
[49, 207, 1, 3],
].map do |sample|
RGB.new(*sample)
end
# Process RGB that does not have the "value" defined using 3, 5 and 7 as "K"
[
RGB.new(1, 2, 100),
RGB.new(10, 20, 30),
RGB.new(8, 5, 20),
RGB.new(237, 45, 100),
RGB.new(1, 50, 101),
RGB.new(67, 121, 12),
].each do |rgb|
values = samples.sort_by { |sample| sample.distance(rgb) }.map { |sample| sample[:value] }
[3, 5, 7].each do |k|
value = values[0, k].inject(Hash.new(0)) { |h, v| h[v] += 1; h }.sort_by { |_, v| -v }.dig(0, 0)
puts format('%<rgb>p: K: %<k>s => %<value>s', rgb: rgb, k: k, value: value)
end
puts('-'* 50)
end
#<RGB 1 2 100>: K: 3 => 1
#<RGB 1 2 100>: K: 5 => 1
#<RGB 1 2 100>: K: 7 => 1
--------------------------------------------------
#<RGB 10 20 30>: K: 3 => 1
#<RGB 10 20 30>: K: 5 => 1
#<RGB 10 20 30>: K: 7 => 1
--------------------------------------------------
#<RGB 8 5 20>: K: 3 => 1
#<RGB 8 5 20>: K: 5 => 1
#<RGB 8 5 20>: K: 7 => 1
--------------------------------------------------
#<RGB 237 45 100>: K: 3 => 2
#<RGB 237 45 100>: K: 5 => 2
#<RGB 237 45 100>: K: 7 => 2
--------------------------------------------------
#<RGB 1 50 101>: K: 3 => 1
#<RGB 1 50 101>: K: 5 => 1
#<RGB 1 50 101>: K: 7 => 1
--------------------------------------------------
#<RGB 67 121 12>: K: 3 => 3
#<RGB 67 121 12>: K: 5 => 3
#<RGB 67 121 12>: K: 7 => 3
--------------------------------------------------
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment