Here’s my solution.
There are arguments which control what the main program does.
-b , or --base is an integer giving the base - this defaults to 10
-t, or --time-limit is an integer giving the number of seconds to run
looking for the happiest number in a give base. This defaults to 600
seconds (10 minutes)
–levels causes an unlimited time run which prints a “happiest” number
of numbers with the same number of digits in the base. Use break to
stop this.
I cache both unhappy numbers and successful paths so that I can
short-circuit things. Not a small memory solution.
== happynumber.rb ==
#! /usr/local/bin/ruby
class Integer
def Integer.reset_happiness_cache
# Integer#to_s(base) works up to a base of 36
@@cached_unhappy_ints = Array.new(37) {|i| Hash.new }
@@cached_happiness_paths = Array.new(37) {|i| {1 =>
[1]}}
self.cache_unhappy([0, 4, 16, 20, 37, 42, 58, 89, 145],
-
end def Integer.show_hc puts "unhappies = #{@@cached_unhappy_ints[10].inspect}" puts "happies = #{@@cached_happiness_paths[10].inspect}" end def squared self * self end def sum_squares_of_digits(base=10) sum = 0 to_s(base).each_byte do |b| sum += (b.chr.to_i(base)).squared end sum end def path_to_happiness(base=10,seen=[]) return nil if Integer.known_unhappy?(self, base) return Integer.cache_unhappy([self], base) if
seen.include?(self)
known_path = Integer.known_happiness_path(self, base)
return known_path.dup if known_path
rest_of_the_way =
sum_squares_of_digits(base).path_to_happiness(base,seen.dup << self)
if rest_of_the_way
ans = rest_of_the_way.unshift(self)
return Integer.cache_happiness_path(ans,
base).dup
else
Integer.cache_unhappy([self], base)
return nil
end
end
def Integer.known_unhappy?(int, base)
@@cached_unhappy_ints[base][int]
end
def Integer.known_happiness_path(int, base)
@@cached_happiness_paths[ base][int]
end
def known_happy?(base)
Integer.known_happiness_path(self, base)
end
def Integer.cache_unhappy(integers, base)
integers.each do | n |
@@cached_unhappy_ints[base][n] = true
end
nil
end
def Integer.cache_happiness_path(path, base)
@@cached_happiness_paths[base][path[0]] = path.dup
path
end
def happy?
!self.path_to_happiness.nil?
end
def happiness
path = path_to_happiness
path_to_happiness ? path_to_happiness.length : 0
end
reset_happiness_cache
end
#generates all numbers in the given range whose digits are in
#nondecreasing order. the order in which the numbers are generated is
#undefined, so it’s possible for 123 to appear before 23, for
#example.
Thanks to Ken B. for this idea, which I generalized to an
arbitrary base
def nondec_digits(range,base=10,&b)
yield 0 if range.first<=0
(1…base-1).each { |x|
nondec_digits_internal(x,x,range,base,&b) }
end
def nondec_digits_internal(last,accum,range,base,&b)
(last…base-1).each do |x|
iaccum=accum*base+x
b.call(iaccum) if range.nil? || range.include?(iaccum)
nondec_digits_internal(x,iaccum,range,base,&b) if
iaccum <= range.last
end
end
enumerate all numbers whose representation in base base
has increasing digits.
def nondec_numbers(base, &b)
start = 0
ten_in_base = “10”.to_i(base)
stop = ten_in_base
while true
nondec_digits((start…stop-1), base, &b)
start = stop
stop *= ten_in_base
end
end
def happiest_in_range(range, base=10)
happiest = 1
max_path_length = 1
probes = 0
nondec_digits(range,base) do |i|
probes += 1
path = i.path_to_happiness(base)
if path && path.length > max_path_length
happiest = i
max_path_length = path.length
end
end
[happiest, max_path_length, probes]
end
def base_levels(base=10)
ten_in_base = “10”.to_i(base)
start = 0
base_n = ten_in_base
n = 1
while true
h, pl, probes = happiest_in_range((start…base_n-1),
base)
puts “one of the happiest #{n} digit base #{base}
numbers is #{h.to_s(base)}, with #{pl} steps after #{probes} probes”
start = base_n
n += 1
base_n *= ten_in_base
end
end
return the happiest number that can be found in time_limit seconds
def happiest(time_limit=60, base=10)
time_to_stop = Time.now + time_limit
happiest = 1
max_path_length = 1
probes = 0
nondec_numbers(base) do |i|
break if Time.now > time_to_stop
probes += 1
path = i.path_to_happiness(base)
if path && path.length > max_path_length
happiest = i
max_path_length = path.length
end
end
puts "happiest base #{base} number found in #{time_limit}
seconds in #{probes} probes"
puts “was #{happiest.to_s(base)} which has a happiness of
#{max_path_length}”
end
require ‘optparse’
opts = OptionParser.new
base = 10
time_limit = 600 # default to 10 minutes
happiest_test = true
opts.on(“-b”, “–base VAL”, Integer) {|val| base = val}
opts.on(“–levels”) {happiest_test = false}
opts.on(“-t”, “–time-limit VAL”, Integer) {|val| time_limit = val}
opts.parse(ARGV)
happiest(time_limit, base) if happiest_test
base_levels(base) unless happiest_test
–
Rick DeNatale
My blog on Ruby
http://talklikeaduck.denhaven2.com/