[QUIZ] Weird Numbers (#57)

R

Ruby Quiz

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

by Martin DeMello

A weird number is defined as a number, n, such that the sum of all its divisors
(excluding n itself) is greater than n, but no subset of its divisors sums up to
exactly n.

Write a program to find all the weird numbers less than a given input.
 
B

Brian Schröder

=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D=
-=3D-=3D-=3D-=3D

I imagine that 1 is also excluded as a divisor, otherwise the answer
would be the empty set always.

Brian

Sorry for the noise...
 
J

JB Eriksson

------=_Part_106140_9405650.1133534872150
Content-Type: text/plain; charset=ISO-8859-1
Content-Transfer-Encoding: quoted-printable
Content-Disposition: inline

please give an example of such a number. english is my second language, and
english math speak is more like my sixth language or so (below
german,swedish techspeak and english techspeak).

The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this quiz
until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!


-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-= =3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D-=3D=
-=3D-=3D-=3D

by Martin DeMello

A weird number is defined as a number, n, such that the sum of all its
divisors
(excluding n itself) is greater than n, but no subset of its divisors sum= s
up to
exactly n.

Write a program to find all the weird numbers less than a given input.

------=_Part_106140_9405650.1133534872150--
 
H

Hampton

Meh. I've done it (dont' worry, not posting it...), but this thing's
gotta be like O(n^3) at least.

-hampton.
 
J

James Edward Gray II

Meh. I've done it (dont' worry, not posting it...), but this thing's
gotta be like O(n^3) at least.

When this quiz was posed to me, the creator thought it would be
interesting to see what optimizations people came up with. Now,
watching the discussions since it has released, I have an idea for
another hefty one. Think outside the box... ;)

James Edward Gray II
 
J

James Edward Gray II

I have a feeling we have the same idea.

Code it up and send it in tomorrow. I'll create my idea if I don't
see a match for it...

James Edward Gray II
 
R

Rob Leslie

My first Ruby Quiz solution...

Cheers,
-rob


#! /usr/bin/ruby
#
# Ruby program to find all the weird numbers less than a given input
# Rob Leslie <[email protected]>
#

class Integer
WEIRD_ODD_UNKNOWN_THRESHOLD = 10 ** 18

def weird?
# Odd numbers less than 10**18 are not weird. (Bob Hearn, July
2005)
return false if self & 1 == 1 && self < WEIRD_ODD_UNKNOWN_THRESHOLD

# Weird numbers are abundant. To be abundant, the sum of the
divisors
# must be greater than twice this number. Equivalently, the sum of
# the proper divisors (excluding the number itself) must be greater
# than this number.
divisors = self.divisors
sum = divisors.inject { |sum, x| sum + x } - self
return false unless sum > self

# Weird numbers are not semiperfect. To be semiperfect, the sum
of a
# subset of the divisors must be equal to this number.
Equivalently,
# the sum of another subset (the complement set) of divisors
must be
# equal to the difference between this number and the sum of all
its
# divisors.
excess = sum - self
addends = divisors.reject { |x| x > excess }
sum = addends.inject { |sum, x| sum + x }

# Trivially semiperfect or non-semiperfect?
return false if sum == excess || addends.include?(excess)
return true if sum < excess

# Default non-semiperfect test (with special case speed
optimization)
self < 222_952 ? 9_272 == self : !excess.sum_of_subset?(addends)
end

def divisors
# Naive implementation; OK for small numbers
list = (1..Math.sqrt(self).floor).select { |i| (self % i).zero? }
list + list.reverse.collect { |i| self / i }
end

def sum_of_subset?(addends)
first = addends.first
return true if self == first
return false unless addends.length > 1
rest = addends[1..-1]
(self - first).sum_of_subset?(rest) or self.sum_of_subset?(rest)
end
end

input = ARGV.shift.to_i

70.upto(input - 1) do |number|
puts number if number.weird?
end
 
R

Rob Leslie

And immediately upon posting my first Ruby Quiz solution, I
discovered a small bug. :-{

Perhaps someone will notice it. :)
 
P

Paolo Capriotti

And immediately upon posting my first Ruby Quiz solution, I
discovered a small bug. :-{

Perhaps someone will notice it. :)

I've found this one:

$ irb -r weird.rb
irb(main):001:0> 4.divisors
=3D> [1, 2, 2, 4]

:)

Paolo
 
P

Paolo Capriotti

This is my solution:

class Integer
def divisors
res =3D []
i =3D 1
while i*i < self
if self % i =3D=3D 0
res << i
end
i +=3D 1
end
(res.size - 1).downto(1) do |k|
res << self / res[k]
end
res << i if i*i =3D=3D 0
res
end
end

def weird(n)
div_sum =3D 0
possible_sums =3D Hash.new
possible_sums[0] =3D true

n.divisors.each do |i|
div_sum +=3D i

possible_sums.keys.each do |s|
new_sum =3D s + i
possible_sums[new_sum] =3D true if new_sum <=3D n
return false if new_sum =3D=3D n
end
end

return div_sum > n
end

n =3D ARGV.shift or exit
n =3D n.to_i
m =3D ARGV.shift
m =3D m.to_i if m
range =3D m ? (n..m) : (1..n)
for i in range
puts i if weird(i)
end
 
R

Rob Leslie

And immediately upon posting my first Ruby Quiz solution, I
discovered a small bug. :-{

Perhaps someone will notice it. :)

I've found this one:

$ irb -r weird.rb
irb(main):001:0> 4.divisors
=> [1, 2, 2, 4]

:)

Exactly right. :)

I had previously written Integer#divisors something like this:

def divisors
list = (2..Math.sqrt(self).floor).select { |i| (self % i).zero? }
list += list.collect { |i| self / i }
[1, *list].uniq
end

but forgot the value of calling Array#uniq upon simplifying and
refactoring.
 
M

Mike Harris

My solution. Error checking is minimal to nonexistent.

class Integer
def each_divisor
for i in 1...self
yield(i) if self%i == 0
end
end

def weird?
sum = 0
each_divisor do |i| sum += i end
return false if sum <= self
each_divisor do |i|
return false if sum-i == self
end
end
end

print "Enter Number (Program will print all lesser weird numbers): "
num = gets
for i in 1...num.to_i
puts i if i.weird?
end
 
J

James Edward Gray II

each_divisor do |i|
return false if sum-i == self
end

Hmm, does that work?

I'm not trying to slander your code. I'm actually asking because I
think it's very elegant, if it's also correct.

You're code removes divisors one-by-one, but what if our divisors
looked something like:

1, ..., 50, ..., 100

And the total sum was just 50 over the number we're testing? The
order we remove divisors might be significant then. Am I right?

James Edward Gray II
 
M

Mike Harris

James said:
Hmm, does that work?

I'm not trying to slander your code. I'm actually asking because I
think it's very elegant, if it's also correct.

You're code removes divisors one-by-one, but what if our divisors
looked something like:

1, ..., 50, ..., 100

And the total sum was just 50 over the number we're testing? The
order we remove divisors might be significant then. Am I right?

James Edward Gray II
ah, I screwed up and scrambled the problem description inbetween reading
it and writing it. My solution only considers subsets of size N-1,
where N is the divisor count.
 
M

Mike Harris

James said:
Hmm, does that work?

I'm not trying to slander your code. I'm actually asking because I
think it's very elegant, if it's also correct.

You're code removes divisors one-by-one, but what if our divisors
looked something like:

1, ..., 50, ..., 100

And the total sum was just 50 over the number we're testing? The
order we remove divisors might be significant then. Am I right?

James Edward Gray II
looks like the proper way I should have done this is to define
Array#each_subset (or possibly Enumerable#each_subset) and cycle through
divisors.each_subset
 
R

Ryan Leavengood

Your solution is definitely in the faster group Bob, though about a
third as fast as the fastest (based on quick and dirty testing on my
laptop.)

In general your algorithm is pretty similar to the faster ones. You
know, it seems like it would be useful to analyze all the solutions to
possibly find Ruby "performance anti-patterns", since there are some
wildly different performance numbers in the solutions for this quiz.

If you think about it, people who complain about Ruby's performance
may just be using some of these "performance anti-patterns", and could
greatly increase performance with some knowledgeable tweaking. For
example the blogger who was analyzing his web server logs and
unnecessarily parsing a lot of dates, which slowed things down a lot.
When I politely corrected him on this and he changed the code,
performance greatly increased, and his view of Ruby improved a lot too
[1].

Ryan

1. http://www.pankaj-k.net/archives/2005/11/revised_ruby_or.html

Here's my solution, made before peeking at any of the other submissions.
It's not too bad; generates the weird numbers up to 100,000 in about 3
minutes on a pokey 233 Celeron.

http://users.adelphia.net/~showaltb/rubyquiz/57/weird.rb

Now I'm off to peek at the other solutions to see how bad my algorithm is=
!
 
M

Moses Hohman

--Apple-Mail-9-892176321
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
charset=US-ASCII;
delsp=yes;
format=flowed

This is my first ruby quiz solution since Quiz #1 : ) Thanks, it was
fun.

Moses


--Apple-Mail-9-892176321
Content-Transfer-Encoding: 7bit
Content-Type: text/x-ruby-script;
x-unix-mode=0644;
name="test_weird.rb"
Content-Disposition: attachment;
filename=test_weird.rb

require 'test/unit'
require 'weird'

class TestWeird < Test::Unit::TestCase
def test_proper_divisors
assert_equal [1], 11.proper_divisors
assert_equal [1, 2, 3, 4, 6], 12.proper_divisors
assert_equal [1, 2, 3, 5, 6, 9, 10, 15, 18, 30, 45], 90.proper_divisors
end

def test_find_partition_from
assert_equal nil, 2.send:)find_partition_from, [1])
assert_equal [2], 2.send:)find_partition_from, [2])
assert_equal [4,1], 5.send:)find_partition_from, [4, 3, 2, 1])
assert_equal [175, 70, 50, 35, 14, 5, 1], 350.send:)find_partition_from, 350.proper_divisors.reverse)
end

def test_abundant
assert_equal [12, 18, 20, 24], (1..25).find_all { |i| i.abundant? }
assert 945.abundant?, "945 not abundant" # test an odd one for good measure, no real reason why
end

def test_semiperfect
assert_equal [6, 12, 18, 20, 24], (1..25).find_all { |i| i.semiperfect? }
end

def test_weird
assert 70.weird?, "70 not weird"
assert !90.weird?, "90 weird"
end

def test_weird_numbers
assert_equal [70, 836], weird_numbers_less_than(1000)
assert_equal [], weird_numbers_less_than(70)
end
end
--Apple-Mail-9-892176321
Content-Transfer-Encoding: 7bit
Content-Type: text/x-ruby-script;
x-unix-mode=0644;
name="weird.rb"
Content-Disposition: attachment;
filename=weird.rb

module Enumerable
# syntactic sugar
def sum
inject(0) { |sum, element| sum += element }
end
end

module Weird
# Returns proper divisors of `self`, i.e. all divisors except `self`.
# Optimization: cached to improve speed - only small benefit for `weird_numbers_less_than`.
def proper_divisors
@proper_divisors ||= calculate_proper_divisors.freeze
end

# True if `self` is abundant, i.e. < the sum of its proper divisors
def abundant?
self < proper_divisors.sum
end

# True if `self` is semiperfect, i.e. at least one subset of `self`'s divisors forms a partition of `self`
def semiperfect?
!find_partition_from(proper_divisors.reverse).nil?
end

# True if self is a weird number, i.e. abundant and not semiperfect.
# Optimization: tests `abundant?` first because it's a lot faster.
def weird?
abundant? and not semiperfect?
end

# Syntactic sugar for Kernel's `weird_numbers_less_than` method below
def even?
self % 2 == 0
end

protected
# Finds one partition of `self` composed of integers taken without replacement from
# `set`. Only finds one, because all we need to know is whether there are any.
#
# Assumptions: set is sorted in decreasing order
# Protected because I want to call it from a unit test via `send`.
def find_partition_from(set)
return nil if set.empty? # recursion termination condition: no partition found

# find index of largest number < self in set (which is assume sorted in decreasing order)
start = nil
set.each_with_index do |element, index|
return [element] if element == self # recursion termination condition: partition found
if element < self # found largest number < self
start = index
break
end
end

# recursively construct partition
partition = nil
unless start.nil?
start.upto(set.length-1) do |index|
break unless partition.nil?
biggest, tailset = set[index], set[(index+1)..-1]
partition = (self - biggest).find_partition_from(tailset)
partition.insert(0, biggest) unless partition.nil?
end
end

partition
end

private
# Actually calculates (i.e. not cached) and returns all proper divisors of `self`.
# Optimization: it turned out that inserting the divisors in sorted order was slower,
# at least the way I tried it. Uniq does not pose much of a burden either.
def calculate_proper_divisors
return [] if self == 1
result = [1]
2.upto(Math.sqrt(self).floor) do |candidate|
# add both candidate and quotient, since they are both divisors
result << candidate << (self / candidate) if self % candidate == 0
end
result.sort.uniq # uniq is needed for perfect squares
end
end

class Fixnum; include Weird; end
class Bignum; include Weird; end # optimistic about CPU power!

# Optimization: `check_odd` parameter: there are no odd weird numbers < 10**19 [Weisstein, 2005], so
# in general you should leave this flag at its default value of `false`. This provides a modest speedup
# (something like a factor of 1.5. I think the reason it's not more is there are few odd abundant numbers).
#
# [Weisstein, 2005] Eric W. Weisstein. "Weird Number." From MathWorld--A Wolfram Web Resource.
# http://mathworld.wolfram.com/WeirdNumber.html
def weird_numbers_less_than(n, check_odd = false)
(1...n).find_all { |number| (check_odd or number.even?) and number.weird? }
end

if __FILE__ == $0
p weird_numbers_less_than(ARGV[0].to_i, (ARGV[1] == 'check_odd'))
end

--Apple-Mail-9-892176321
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
charset=US-ASCII;
delsp=yes;
format=flowed




The three rules of Ruby Quiz:

1. Please do not post any solutions or spoiler discussion for this
quiz until
48 hours have passed from the time on this message.

2. Support Ruby Quiz by submitting ideas as often as you can:

http://www.rubyquiz.com/

3. Enjoy!

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
=-=-=-=-=-=-=

by Martin DeMello

A weird number is defined as a number, n, such that the sum of all
its divisors
(excluding n itself) is greater than n, but no subset of its
divisors sums up to
exactly n.

Write a program to find all the weird numbers less than a given input.


--Apple-Mail-9-892176321--
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,065
Latest member
OrderGreenAcreCBD

Latest Threads

Top