Skip to content

Instantly share code, notes, and snippets.

@zacheryph
Last active August 29, 2015 13:56
Show Gist options
  • Save zacheryph/8935421 to your computer and use it in GitHub Desktop.
Save zacheryph/8935421 to your computer and use it in GitHub Desktop.
spread amount over buckets with no under/over
class BucketSplit
class InvalidSpread < StandardError ; end
def initialize(total, splits, precision = 2)
self.precision = precision
self.total = total.round(precision)
self.splits = splits
self.buckets = calculate_buckets
end
def value_for(pct)
buckets[pct] || buckets[pct / 100.0]
end
# private
attr_accessor :total, :splits, :cast, :buckets, :precision
def calculate_buckets
cursor = {total: total, pct_used: 0, bucket: []}
result = sorted_splits.inject(cursor) {|c, pct| process_split(c, pct)}
Hash[ *result[:bucket].flatten ]
end
def process_split(cursor, percent)
value = (cursor[:total] * (percent / (1 - cursor[:pct_used]))).round(precision)
cursor[:bucket] << [percent, value]
cursor.merge(total: cursor[:total] - value, pct_used: cursor[:pct_used] + percent)
end
def normalized_percentages
case splits.sum
when 1 then splits
when 100 then splits.map { |pct| pct / 100.0 }
else raise InvalidSpread, "Splits dont amount to 100%"
end
end
def sorted_splits
normalized_percentages
.map { |pct| {pct: pct, fract: ((total * pct) * (10 ** precision)) % 1} }
.sort_by { |pair| pair[:fract] }
.reverse
.map { |pair| pair[:pct] }
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment