Skip to content

Instantly share code, notes, and snippets.

@craigw
Created February 5, 2014 17:40
Show Gist options
  • Save craigw/8829219 to your computer and use it in GitHub Desktop.
Save craigw/8829219 to your computer and use it in GitHub Desktop.
## Why?
# Because I frequently have to shift numbers to denser encodings to pass around,
# Ruby can't handle base 62 natively, I don't like the base 62 encoders that
# monkey patch stdlib... but mostly because I can.
class BaseNumber < Struct.new(:representation, :base)
def to_s
representation
end
def to_i
BaseEncoder.new(base).decode self
end
def rebase new_base
BaseEncoder.new(new_base).encode to_i
end
end
class BaseEncoder
attr_accessor :base
def initialize base
self.base = base
raise "I only support bases 2 and above" if base < 2
raise "I only support up to base 62" if base > 62
end
def case_sensitive?
base > 36
end
def primitives
@primitives ||= ((0..9).collect { |i| i.to_s } + ('A'..'Z').to_a + ('a'..'z').to_a)[0,base]
end
def decode subject
r = subject.representation
r.upcase! unless BaseEncoder.new(subject.base).case_sensitive?
s = r.reverse.split('')
total = 0
s.each_with_index do |char, index|
if ord = primitives.index(char)
total += ord * (base ** index)
else
raise ArgumentError, "#{subject} has #{char} which is not valid"
end
end
total
end
def encode subject
subject = BaseNumber.new subject.to_s, 10 unless subject.kind_of? BaseNumber
return subject if subject.base == base
i = BaseEncoder.new(subject.base).decode(subject)
s = ''
while i > 0
s << primitives[i.modulo(base)]
i /= base
end
s = '0' if s == ''
BaseNumber.new s.reverse, base
end
end
if $0 == __FILE__
bases = [ 2, 3, 5, 10, 36, 37, 50, 62 ]
format_string = [ "%-10.10s", *bases.map { |_| "%-10.10s" } ].join(' | ')
puts format_string % [ 'raw', *bases ]
(0..1023).each do |n|
n = BaseNumber.new n.to_s, 10
puts format_string % [ n, *bases.map { |b| n.rebase b } ]
end
require 'benchmark'
n = 10_000
Benchmark.bmbm do |x|
(2..62).each do |b|
z = BaseNumber.new '1000', 10
x.report("Base10 -> Base#{b}") { n.times { z.rebase b } }
end
(2..62).each do |b|
z = BaseNumber.new('1000', 10).rebase b
x.report("Base#{b} -> Base10") { n.times { z.rebase 10 } }
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment