From 2c4460f4c1cf79c6715dabad3e60511d37ac527d Mon Sep 17 00:00:00 2001 From: universato Date: Fri, 11 Jun 2021 01:51:08 +0900 Subject: [PATCH 1/4] Add divisors methods The main idea and code was provided by tompng-san. --- lib/prime.rb | 28 ++++++++++++++++++++++++++ test/test_prime.rb | 49 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/lib/prime.rb b/lib/prime.rb index fd9d6ac..532c45b 100644 --- a/lib/prime.rb +++ b/lib/prime.rb @@ -16,6 +16,34 @@ require "forwardable" class Integer + # Returns the positive divisors of +self+ if +self+ is positive. + # + # == Example + # 6.divisors #=> [1, 2, 3, 6] + # 7.divisors #=> [1, 7] + # 8.divisors #=> [1, 2, 4, 8] + def divisors + if prime? + [1, self] + elsif self == 1 + [1] + else + xs = prime_division.map{ |p, n| Array.new(n + 1){ |e| p**e } } + x = xs.pop + x.product(*xs).map{ |t| t.inject(:*) }.sort + end + end + + # Iterates the given block for each divisor of +self+. + # + # == Example + # ds = [] + # 10.divisors{ |d| ds << d } + # ds #=> [1, 2, 5, 10] + def each_divisor + block_given? ? divisors.each{ |d| yield(d) } : enum_for(:each_divisor) + end + # Re-composes a prime factorization and returns the product. # # See Prime#int_from_prime_division for more details. diff --git a/test/test_prime.rb b/test/test_prime.rb index a0e8af5..db103b4 100644 --- a/test/test_prime.rb +++ b/test/test_prime.rb @@ -214,6 +214,55 @@ def test_rewind end class TestInteger < Test::Unit::TestCase + def test_divisors + assert_equal [1], 1.divisors + assert_equal [1, 2], 2.divisors + assert_equal [1, 3], 3.divisors + assert_equal [1, 2, 4], 4.divisors + assert_equal [1, 5], 5.divisors + assert_equal [1, 2, 3, 6], 6.divisors + assert_equal [1, 7], 7.divisors + assert_equal [1, 2, 4, 8], 8.divisors + assert_equal [1, 3, 9], 9.divisors + assert_equal [1, 2, 5, 10], 10.divisors + assert_equal [1, 2, 4, 5, 10, 20, 25, 50, 100], 100.divisors + + # large prime number + assert_equal [1, 2**31-1], (2**31-1).divisors + assert_equal [1, 1_000_000_007], 1_000_000_007.divisors + assert_equal [1, 1_000_000_009], 1_000_000_009.divisors + assert_equal [1, 67_280_421_310_721], 67_280_421_310_721.divisors + + # large composite number + p1 = 2**13-1 + p2 = 2**17-1 + assert_equal [1, p1, p2, p1 * p2], (p1 * p2).divisors + + assert_raises(ZeroDivisionError){ 0.divisors } + assert_equal [-1, 1], -1.divisors + assert_equal [-2, -1, 1, 2], -2.divisors + assert_equal [-3, -1, 1, 3], -3.divisors + assert_equal [-4, -2, -1, 1, 2, 4], -4.divisors + assert_equal [-6, -3, -2, -1, 1, 2, 3, 6], -6.divisors + end + + def test_each_divisor + d10 = [] + 10.each_divisor{ |d| d10 << d } + assert_equal [1, 2, 5, 10], d10 + + assert_equal [1], 1.each_divisor.to_a + assert_equal [1, 3], 3.each_divisor.to_a + assert_equal [1, 2, 4], 4.each_divisor.to_a + assert_equal [1, 2, 3, 6], 6.each_divisor.to_a + + assert_raises(ZeroDivisionError){ 0.each_divisor.to_a } + assert_equal [-1, 1], -1.each_divisor.to_a + assert_equal [-2, -1, 1, 2], -2.each_divisor.to_a + assert_equal [-4, -2, -1, 1, 2, 4], -4.each_divisor.to_a + assert_equal [-6, -3, -2, -1, 1, 2, 3, 6], -6.each_divisor.to_a + end + def test_prime_division pd = PRIMES.inject(&:*).prime_division assert_equal PRIMES.map{|p| [p, 1]}, pd From 81a54243b878d9a5efef7897a20b64126463bd05 Mon Sep 17 00:00:00 2001 From: universato Date: Sat, 26 Jun 2021 15:53:05 +0900 Subject: [PATCH 2/4] Revise document for each_divisor --- lib/prime.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/prime.rb b/lib/prime.rb index 532c45b..c05d66b 100644 --- a/lib/prime.rb +++ b/lib/prime.rb @@ -38,7 +38,7 @@ def divisors # # == Example # ds = [] - # 10.divisors{ |d| ds << d } + # 10.each_divisor{ |d| ds << d } # ds #=> [1, 2, 5, 10] def each_divisor block_given? ? divisors.each{ |d| yield(d) } : enum_for(:each_divisor) From df3628861ee129fd45c383d9997821294743f9ff Mon Sep 17 00:00:00 2001 From: universato Date: Sat, 26 Jun 2021 16:47:24 +0900 Subject: [PATCH 3/4] Add some tests for each_divisor Add some tests to check the result without to_a being called. --- test/test_prime.rb | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/test/test_prime.rb b/test/test_prime.rb index db103b4..361b7bd 100644 --- a/test/test_prime.rb +++ b/test/test_prime.rb @@ -246,10 +246,27 @@ def test_divisors assert_equal [-6, -3, -2, -1, 1, 2, 3, 6], -6.divisors end - def test_each_divisor - d10 = [] - 10.each_divisor{ |d| d10 << d } - assert_equal [1, 2, 5, 10], d10 + def test_each_divisor_with_block + ds = [] + 1.each_divisor{ |d| ds << d } + assert_equal [1], ds + + ds = [] + 10.each_divisor{ |d| ds << d } + assert_equal [1, 2, 5, 10], ds + + assert_equal [1, 3], 3.each_divisor{} + assert_raises(ZeroDivisionError){ 0.each_divisor{} } + + ds = [] + -2.each_divisor{ |d| ds << d } + assert_equal [-2, -1, 1, 2], ds + end + + def test_each_divisor_without_blcok + enum = 5.each_divisor + assert_respond_to(enum, :each) + assert_kind_of(Enumerable, enum) assert_equal [1], 1.each_divisor.to_a assert_equal [1, 3], 3.each_divisor.to_a From d664649a3d29b49119f4ec30feaedde9b5880398 Mon Sep 17 00:00:00 2001 From: universato Date: Sat, 26 Jun 2021 20:03:51 +0900 Subject: [PATCH 4/4] Improve performance and change API Improve divisors of small integer. Change API of zero and negative integers. --- lib/prime.rb | 28 +++++++++++++++++++++++++--- test/test_prime.rb | 27 ++++++++++++++------------- 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/lib/prime.rb b/lib/prime.rb index c05d66b..f26c9cf 100644 --- a/lib/prime.rb +++ b/lib/prime.rb @@ -16,17 +16,21 @@ require "forwardable" class Integer - # Returns the positive divisors of +self+ if +self+ is positive. + # Returns the positive divisors of +self+ # # == Example # 6.divisors #=> [1, 2, 3, 6] # 7.divisors #=> [1, 7] # 8.divisors #=> [1, 2, 4, 8] def divisors + if self < 1_000_000 + return divisors_for_small_integer if self > 0 + return abs.divisors if self < 0 + raise ArgumentError.new("The divisors of 0 is all integer except 0") + end + if prime? [1, self] - elsif self == 1 - [1] else xs = prime_division.map{ |p, n| Array.new(n + 1){ |e| p**e } } x = xs.pop @@ -34,6 +38,24 @@ def divisors end end + private def divisors_for_small_integer + # O(√n) algorithm + n = self + s = Integer.sqrt(n) + res1 = [] + res2 = [] + i = 1 + while i <= s + if self % i == 0 + res1 << i + res2.unshift(n / i) + end + i += 1 + end + res1.pop if s * s == n + res1.concat(res2) + end + # Iterates the given block for each divisor of +self+. # # == Example diff --git a/test/test_prime.rb b/test/test_prime.rb index 361b7bd..7631566 100644 --- a/test/test_prime.rb +++ b/test/test_prime.rb @@ -238,12 +238,13 @@ def test_divisors p2 = 2**17-1 assert_equal [1, p1, p2, p1 * p2], (p1 * p2).divisors - assert_raises(ZeroDivisionError){ 0.divisors } - assert_equal [-1, 1], -1.divisors - assert_equal [-2, -1, 1, 2], -2.divisors - assert_equal [-3, -1, 1, 3], -3.divisors - assert_equal [-4, -2, -1, 1, 2, 4], -4.divisors - assert_equal [-6, -3, -2, -1, 1, 2, 3, 6], -6.divisors + assert_raises(ArgumentError){ 0.divisors } + assert_equal [1], -1.divisors + assert_equal [1, 2], -2.divisors + assert_equal [1, 3], -3.divisors + assert_equal [1, 2, 4], -4.divisors + assert_equal [1, 2, 3, 6], -6.divisors + assert_equal [1, 1_000_000_007], -1_000_000_007.divisors end def test_each_divisor_with_block @@ -256,11 +257,11 @@ def test_each_divisor_with_block assert_equal [1, 2, 5, 10], ds assert_equal [1, 3], 3.each_divisor{} - assert_raises(ZeroDivisionError){ 0.each_divisor{} } + assert_raises(ArgumentError){ 0.each_divisor{} } ds = [] -2.each_divisor{ |d| ds << d } - assert_equal [-2, -1, 1, 2], ds + assert_equal [1, 2], ds end def test_each_divisor_without_blcok @@ -273,11 +274,11 @@ def test_each_divisor_without_blcok assert_equal [1, 2, 4], 4.each_divisor.to_a assert_equal [1, 2, 3, 6], 6.each_divisor.to_a - assert_raises(ZeroDivisionError){ 0.each_divisor.to_a } - assert_equal [-1, 1], -1.each_divisor.to_a - assert_equal [-2, -1, 1, 2], -2.each_divisor.to_a - assert_equal [-4, -2, -1, 1, 2, 4], -4.each_divisor.to_a - assert_equal [-6, -3, -2, -1, 1, 2, 3, 6], -6.each_divisor.to_a + assert_raises(ArgumentError){ 0.each_divisor.to_a } + assert_equal [1], -1.each_divisor.to_a + assert_equal [1, 2], -2.each_divisor.to_a + assert_equal [1, 2, 4], -4.each_divisor.to_a + assert_equal [1, 2, 3, 6], -6.each_divisor.to_a end def test_prime_division