Here is my solution. It is pretty fast, but not quite as fast as
Rob's. For example his takes 6.797 seconds to calculate all weird
numbers up to 50000, whereas mine takes 7.375. Our solutions are quite
similar. Some of the other solutions are REALLY SLOW though. Probably
one of the biggest optimizations was using the square root of a given
number to calculate half the divisors, then use those to get the other
half. For example if 2 is a divisor of 14, then so is 14/2 = 7. For
big numbers this can be a massive speed-up.
Ryan
class Array
def sum
inject(0) do |result, i|
result + i
end
end
end
class Integer
def weird?
# No odd numbers are weird within reasonable limits.
return false if self % 2 == 1
# A weird number is abundant but not semi-perfect.
divisors = calc_divisors
abundance = divisors.sum - 2 * self
# First make sure the number is abundant.
if abundance > 0
# Now see if the number is semi-perfect. If it is, it isn't weird.
# First thing see if the abundance is in the divisors.
if divisors.include?(abundance)
false
else
# Now see if any combination sums of divisors yields the abundance.
# We reject any divisors greater than the abundance and reverse the
# result to try and get sums close to the abundance sooner.
to_search = divisors.reject{|i| i > abundance}.reverse
sum = to_search.sum
if sum == abundance
false
elsif sum < abundance
true
else
not abundance.sum_in_subset?(to_search)
end
end
else
false
end
end
def calc_divisors
res=[1]
2.upto(Math.sqrt(self).floor) do |i|
if self % i == 0
res << i
end
end
res.reverse.each do |i|
res << self / i
end
res
end
def sum_in_subset?(a)
if self < 0
false
elsif a.include?(self)
true
else
if a.length == 1
false
else
f = a.first
remaining = a[1..-1]
(self - f).sum_in_subset?(remaining) or sum_in_subset?(remaining)
end
end
end
end
if $0 == __FILE__
if ARGV.length < 1
puts "Usage: #$0 <upper limit>"
exit(1)
end
puts "Weird numbers up to and including #{ARGV[0]}:"
70.upto(ARGV[0].to_i) do |i|
puts i if i.weird?
end
end