class SlideRule::DistanceCalculator

Attributes

rules[RW]

Public Class Methods

new(rules) click to toggle source
# File lib/slide_rule/distance_calculator.rb, line 5
def initialize(rules)
  @rules = prepare_rules(rules)
end

Public Instance Methods

calculate_distance(i1, i2) click to toggle source

All distances represented as 0..1

0 = perfect match
1 = farthest distance

Calculate distances for all attributes, then apply weight, and average them out. Rules format: {

:attribute_name => {
  :weight => 0.90,
  :calculator => :distance_calculator,
  :threshold => 30
}

}

# File lib/slide_rule/distance_calculator.rb, line 64
def calculate_distance(i1, i2)
  calculate_weighted_distances(i1, i2).reduce(0.0) do |distance, obj|
    distance + (obj[:distance] * obj[:weight])
  end
end
closest_match(obj, array, threshold = 1.0) click to toggle source
# File lib/slide_rule/distance_calculator.rb, line 25
def closest_match(obj, array, threshold = 1.0)
  matches(obj, array, threshold).sort_by { |match| match[:distance] }.first
end
closest_matching_item(obj, array, threshold = 1.0) click to toggle source
# File lib/slide_rule/distance_calculator.rb, line 29
def closest_matching_item(obj, array, threshold = 1.0)
  match = closest_match(obj, array, threshold)
  return nil if match.nil?

  match[:item]
end
group(array) click to toggle source

TODO: Figure this out. Very inefficient! Probably should calculate using a suggestions algorythm

# File lib/slide_rule/distance_calculator.rb, line 11
def group(array)
  array.map do |item|
    {
      item: item,
      matches: (array - [item]).map do |item_cmp|
        {
          match: item_cmp,
          distance: calculate_distance(item, item_cmp)
        }
      end
    }
  end
end
is_match?(obj_1, obj_2, threshold) click to toggle source
# File lib/slide_rule/distance_calculator.rb, line 36
def is_match?(obj_1, obj_2, threshold)
  distance = calculate_distance(obj_1, obj_2)
  distance < threshold
end
matches(obj, array, threshold) click to toggle source
# File lib/slide_rule/distance_calculator.rb, line 41
def matches(obj, array, threshold)
  array.map do |item|
    distance = calculate_distance(obj, item)
    next nil unless distance <= threshold
    {
      item: item,
      distance: distance
    }
  end.compact
end

Private Instance Methods

calculate_weighted_distances(i1, i2) click to toggle source
# File lib/slide_rule/distance_calculator.rb, line 72
def calculate_weighted_distances(i1, i2)
  distances = @rules.map do |attribute, options|
    val1 = i1.send(attribute)
    val2 = i2.send(attribute)
    distance = options[:calculator].calculate(val1, val2, options)
    next { distance: distance.to_f, weight: options[:weight] } unless distance.nil?

    nil
  end
  normalize_weights_array(distances) if distances.compact!

  distances
end
get_calculator(calculator) click to toggle source
# File lib/slide_rule/distance_calculator.rb, line 86
def get_calculator(calculator)
  return calculator.new if calculator.is_a?(Class)

  klass_name = calculator.to_s.split('_').collect(&:capitalize).join.to_s
  klass = begin
    ::SlideRule::DistanceCalculators.const_get(klass_name)
  rescue ::NameError
    nil
  end

  fail ArgumentError, "Unable to find calculator #{klass_name}" if klass.nil?

  klass.new
end
normalize_weights(rules) click to toggle source

Ensures all weights add up to 1.0

# File lib/slide_rule/distance_calculator.rb, line 103
def normalize_weights(rules)
  weight_total = rules.map { |_attr, rule| rule[:weight] }.reduce(0.0, &:+)
  rules.each do |_attr, rule|
    rule[:weight] = rule[:weight] / weight_total
  end
end
normalize_weights_array(rules) click to toggle source

Ensures all weights add up to 1.0 in array of hashes

# File lib/slide_rule/distance_calculator.rb, line 112
def normalize_weights_array(rules)
  weight_total = rules.map { |rule| rule[:weight] }.reduce(0.0, &:+)
  rules.each do |rule|
    rule[:weight] = rule[:weight] / weight_total
  end
end
prepare_rules(rules) click to toggle source

Prepares a duplicate of given rules hash with normalized weights and calculator instances

# File lib/slide_rule/distance_calculator.rb, line 121
def prepare_rules(rules)
  prepared_rules = rules.each_with_object({}) do |(attribute, rule), copy|
    rule = copy[attribute] = safe_dup(rule)

    if rule[:type]
      puts 'Rule key `:type` is deprecated. Use `:calculator` instead.'
      rule[:calculator] = rule[:type]
    end

    rule[:calculator] = get_calculator(rule[:calculator])

    copy
  end
  prepared_rules = normalize_weights(prepared_rules)

  prepared_rules
end
safe_dup(obj) click to toggle source
# File lib/slide_rule/distance_calculator.rb, line 139
def safe_dup(obj)
  obj.dup
rescue
  obj
end