Ответ 1
Это был интересный вопрос, поэтому я немного переборщил:
class Integer
def to_base(base=10)
return [0] if zero?
raise ArgumentError, 'base must be greater than zero' unless base > 0
num = abs
return [1] * num if base == 1
[].tap do |digits|
while num > 0
digits.unshift num % base
num /= base
end
end
end
end
Это работает для произвольных баз. Он работает только для целых чисел, хотя нет причин, по которым он не может быть расширен для работы с любым произвольным числом. Кроме того, он игнорирует знак числа. Опять же, нет причин, по которым он должен это делать, но в основном я не хотел придумывать соглашение о возврате знака в возвращаемом значении.
class Integer
old_to_s = instance_method(:to_s)
define_method :to_s do |base=10, mapping=nil, sep=''|
return old_to_s.bind(self).(base) unless mapping || base > 36
mapping ||= '0123456789abcdefghijklmnopqrstuvwxyz'
return to_base(base).map {|digit| mapping[digit].to_s }.join(sep)
end
end
[Fixnum, Bignum].each do |klass|
old_to_s = klass.instance_method(:to_s)
klass.send :define_method, :to_s do |base=10, mapping=nil, sep=''|
return old_to_s.bind(self).(base) unless mapping || base > 36
return super(base, mapping, sep) if mapping
return super(base)
end
end
Я также расширил метод to_s
, чтобы он работал с базами больше 36. Если вы хотите использовать базу больше 36, вам нужно передать объект сопоставления, который отображает "цифры" в строки. (Ну, на самом деле, все, что требуется, это то, что вы предоставляете объект, который отвечает на []
, и возвращает что-то, что отвечает на to_s
. Итак, строка идеальна, но, например, массив целых чисел также работает.)
Он также принимает необязательный разделитель, который используется для разделения цифр.
Например, это позволяет отформатировать адрес IPv4, обрабатывая его как номер базы 256 и используя идентификатор для отображения и '.'
в качестве разделителя:
2_078_934_278.to_s(256, Array.new(256) {|i| i }, '.') # => '123.234.5.6'
Здесь (неполный) testuite:
require 'test/unit'
class TestBaseConversion < Test::Unit::TestCase
def test_that_83992_in_base_85_is_11_53_12
assert_equal [11, 53, 12], 83992.to_base(85)
end
def test_that_83992_in_base_37_is_1_24_13_2
assert_equal [1, 24, 13, 2], 83992.to_base(37)
end
def test_that_84026_in_base_37_is_1_24_13_36
assert_equal [1, 24, 13, 36], 84026.to_base(37)
end
def test_that_0_in_any_base_is_0
100.times do |base|
assert_equal [0], 0.to_base(base)
assert_equal [0], 0.to_base(1 << base)
assert_equal [0], 0.to_base(base << base)
end
end
def test_that_84026_in_base_37_prints_1od_
assert_equal '1od_', 84026.to_s(37, '0123456789abcdefghijklmnopqrstuvwxyz_')
end
def test_that_ip_address_formatting_works
addr = 2_078_934_278
assert_equal '123.234.5.6', addr.to_s(256, (0..255).to_a, '.')
assert_equal '123.234.5.6', addr.to_s(256, Array.new(256) {|i| i}, '.')
end
def test_that_old_to_s_still_works
assert_equal '84026', 84026.to_s
assert_equal '1su2', 84026.to_s(36)
end
end