Давайте начнем с создания метода, аргумент которого digits
является массивом цифр и который возвращает ("подсчитывающий") хеш, ключи которого являются цифрами 0-9
и значения которого digits.count(d)
для каждого ключа d
,
def count_digits(digits)
digits.each_with_object((0..9).to_a.product([0]).to_h) { |d,h| h[d] += 1 }
end
Например,
digits = 35145.digits
#=> [5, 4, 1, 5, 3]
digit_counts = count_digits(digits)
#=> {0=>0, 1=>1, 2=>0, 3=>1, 4=>1, 5=>2, 6=>0, 7=>0, 8=>0, 9=>0}
(См. Массив # product и Целые # цифры ). Теперь мы можем написать метод, который возвращает желаемый результат.
def next_smallest(n)
digits = n.digits
digit_counts = count_digits(digits)
(n-1).downto(0).find { |m| count_digits(m.digits) == digit_counts }
end
require 'time'
def doit(n)
t = Time.now
n = next_smallest(n)
puts "Elapsed time in seconds = #{(Time.now-t).round(7)}"
n
end
doit 44444
# Elapsed time in seconds = 0.2008061
#=> nil
doit 35154
# Elapsed time in seconds = 5.18e-05
#=> 35145
doit 35154217464215716524742391453862
# Elapsed time in seconds = 0.0005618
#=> 35154217464215716524742391453826
doit 351542174642157165247423914538623999
# Elapsed time in seconds = 3.4207082
#=> 351542174642157165247423914538399962
Хотя это может быть несколько академично, мы можем добиться большего. Учитывая digits
и digit_counts
выше, для обновления digits
для
n = 35153
мы можем написать (потому что последняя цифра больше нуля):
digits[0] -= 1
digits
#=> [4, 4, 1, 5, 3]
Аналогично, после создания глубокой копии digit_counts
:
h = digit_counts.transform_values(&:itself)
#=> {0=>0, 1=>1, 2=>0, 3=>1, 4=>1, 5=>2, 6=>0, 7=>0, 8=>0, 9=>0}
мы можем обновить этот хеш, написав:
d0 = digits.first
#=> 4
h[d0] += 1
h[d0+1] -= 1
h #=> {0=>0, 1=>1, 2=>0, 3=>1, 4=>2, 5=>1, 6=>0, 7=>0, 8=>0, 9=>0}
вместо звонка count_digits
. (См. Hash # transform_values ). Теперь давайте изменим метод следующим образом.
def next_smallest(n)
digits = n.digits
digit_counts = count_digits(digits)
h = digit_counts.transform_values(&:itself)
(n-1).downto(10).find do |m|
d0 = digits.first
if d0 > 0
digits[0] -= 1
h[d0] -= 1
h[d0-1] += 1
else
digits = m.digits
h = count_digits(digits)
end
h == digit_counts
end
end
doit 44444
# Elapsed time in seconds = 0.0607323
#=> nil
doit 35154
# Elapsed time in seconds = 0.0001582
#=> 35145
doit 35154217464215716524742391453862
# Elapsed time in seconds = 0.000216
#=> 35154217464215716524742391453826
doit 351542174642157165247423914538623999
# Elapsed time in seconds = 0.5180595
#=> 351542174642157165247423914538399962
Дальнейшие улучшения возможны в случае, когда последняя цифра равна нулю (чтобы не вызывать count_digits
). Если, например,
n = 2130
digits = n.digits
#=> [0, 3, 1, 2]
h = count_digits(digits)
#=> {0=>1, 1=>1, 2=>1, 3=>1, 4=>0, 5=>0, 6=>0, 7=>0, 8=>0, 9=>0}
digits
и h
можно обновить для n = 2129
следующим образом:
n -= 1
#=> 2129
d1 = digits[1]
#=> 3
digits
#=> [9, 2, 1, 2]
h[0] -= 1
h[9] += 1
h[d1] -= 1
h[d1-1] += 1
h #=> {0=>0, 1=>1, 2=>2, 3=>0, 4=>0, 5=>0, 6=>0, 7=>0, 8=>0, 9=>1}
Можно сделать что-то подобное, если n = 2100
или 2000
, но быстро становится быстрее просто позвонить count_digits
,