Алгоритм преобразования числа base-10 в число base-N
Я ищу способ преобразовать число base-10 в число base-N, где N может быть большим. В частности, я смотрю на преобразование в base-85 и обратно. Кто-нибудь знает простой алгоритм для выполнения преобразования? В идеале это обеспечило бы что-то вроде:
to_radix(83992, 85) -> [11, 53, 12]
любые идеи!
Роха
8 ответов
это был интересный вопрос, поэтому я немного переборщил:
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'
вот (неполный) testsuite:
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
псевдокод для этого довольно прост. Для основания 85 из целых чисел без знака:
digits := '';
while (number > 0)
digit := number % 85
digits := base85Digit(digit) + digits
number /= 85 // integer division so the remainder is rounded off
end while
и к базе 10:
mult := 1
result := 0
for each digit in digits // starting from the rightmost working left
result += base10(digit) * mult
mult *= 85
end for
просто общий алгоритм псевдокод:
- инициализировать пустой список
- взять текущий номер mod базы, сохранить результат в передней части списка
- разделить текущее число на базу и пол (целочисленное деление делает это отлично)
- если результат все еще больше нуля, повторите в #2
83992 / 85 = 988, reminder 12
988 / 85 = 11, reminder 53
11 / 85 = 0, reminder 11
напишите напоминание в обратном порядке: 11, 53, 12, чтобы получить номер базы-85.
чтобы вернуть его:
11 * 85^2 + 53 * 85^1 + 12 * 85^0 = 83992
самый простой алгоритм, который я могу придумать, это (в псевдо-коде):
N = base-10 number
1) N mod 85 = 1st number
2) tempVal = floor(N/85)
3) if(tempVal > 0 && tempVal < 85) then
tempVal= 2nd number
else
2nd number = (tempVal mod 85), then goto step (2), replacing N with N1
база 85 особенно полезна для кодирования двоичных данных ASCII, для чего, я полагаю, вы ее используете. (Однако, если это то, почему вы должны спросить себя, действительно ли это стоит дополнительных хлопот и будет ли база 64 недостаточно хороша.)
Если вы используете это как схему кодирования, ваша задача будет заключаться в преобразовании целых чисел (4 байта) в группы из 5 чисел base85. (Как вы справляетесь с вещами, которые не кратны 4 байтам вы, как правило, в конце заполняются нулями. Подробности смотрите на странице Википедии на базе 85.)
основной алгоритм довольно прост: возьмите остаток на деление 85 при упаковке в базу 85, затем разделите и повторите, пока вы не закончите. Чтобы вернуться назад, несколько раз добавьте значение и умножьте на 85, пока не закончите. Я не очень хорошо знаком с Ruby, поэтому код здесь-стиль C/C++/Javaish, который, надеюсь, вы можете интерпретировать:
// To base 85
unsigned int n = // your number
byte b85[5]; // What you want to fill
for (int i=0 ; i<5 ; i++) {
b85[4-i] = (n%85); // Fill backwards to get most significant value at front
n = n/85;
}
// From base 85
n = 0;
for (int i=0 ; i< 5 ; i++) {
n = n*85 + b85[i];
}
Это не беспокоясь о переполнении, не беспокоясь о добавлении 33, чтобы попасть в диапазон ASCII, и не беспокоясь о соглашении, что ноль кодируется как z
не !!!!!
и так далее.
потому что я чувствую, что рекурсия недопредставлена в ответах, я даю следующий грубый черновик
def to_radix(int, radix)
int == 0 ? [] : (to_radix(int / radix, radix) + [int % radix])
end