class WeightedSampler::Base

Public Class Methods

new(enum, seed: nil, skip_normalization: false) click to toggle source
# File lib/weighted_sampler.rb, line 13
def initialize(enum, seed: nil, skip_normalization: false)
  @random = Random.new(seed) unless seed.nil?

  if enum.is_a?(Hash)
    @p_margins = normalized_margins(enum.values, skip_normalization)
    @keys = enum.keys
  elsif enum.is_a?(Array)
    @p_margins = normalized_margins(enum, skip_normalization)
    @keys = [*0...enum.size]
  end

  return unless @p_margins.nil? || @keys.nil? || @keys.empty?

  raise ArgumentError, 'input structure must be a non-empty Hash or Array'
end

Public Instance Methods

sample() click to toggle source
# File lib/weighted_sampler.rb, line 29
def sample
  pick = @random ? @random.rand : rand

  idx = @p_margins.find_index { |margin| pick < margin }
  idx ||= @p_margins.count - 1 # safe assignment if last margin was not good enough

  @keys[idx]
end

Private Instance Methods

incremental_margins(array) click to toggle source

convert probs like [0.1, 0.2, 0.3, 0.4] to incremental margins [0.1, 0.3, 0.6, 1.0]

# File lib/weighted_sampler.rb, line 55
def incremental_margins(array)
  start = 0.0
  margins = array.map do |v|
    res = v + start
    start = res

    res
  end

  raise 'normalized probabilities total is not 1' if (start - 1.0).abs > ERROR_ALLOWANCE

  margins
end
normalize_probabilities(array) click to toggle source
# File lib/weighted_sampler.rb, line 47
def normalize_probabilities(array)
  sum = array.inject(&:+).to_f

  array.map { |el| el / sum }
end
normalized_margins(array, skip_normalization) click to toggle source
# File lib/weighted_sampler.rb, line 40
def normalized_margins(array, skip_normalization)
  raise ArgumentError, 'weights can be only positive' if array.any?(&:negative?)

  probabilities = skip_normalization ? array : normalize_probabilities(array)
  incremental_margins probabilities
end