From fce093432eadc191b3647f116a9c2f6748efda3e Mon Sep 17 00:00:00 2001 From: yugui Date: Wed, 3 Sep 2008 13:57:21 +0000 Subject: * lib/mathn.rb (Integer): moved into prime.rb. (Prime): ditto. * lib/prime.rb (Integer): moved from mathn.rb. (Integer.each_prime): added. (Integer#prime?): added. (Prime): moved from mathn.rb. Its implmentation was rewritten. see [ruby-dev:35863]. And patched by Keiju ISHITSUKA , see [ruby-dev:36128]. (Prime.new): obsolete. (Prime.instance): added. (Prime.each): added. (Prime.int_from_prime_division): added. (Prime.prime_division): added. (Prime.prime?): added. Patch by TOYOFUKU Chikanobu in [ruby-dev:36067]. (Prime.cache): removed. (Prime.primes): removed. (Prime.primes_so_far): removed. (Prime#int_from_prime_division): added. (Prime#prime_division): added. (Prime#prime?): added. (Prime#primes): removed. (Prime#primes_so_far): removed. (Prime::PseudoPrmeGenerator): added. (Prime::EratosthenesGenerator): added. (Prime::TrialDivisionGenerator): added. (Prime::Generator23): added. (Prime::TrialDivision): added. Extracted from the previous implementation of Prime by Keiju ISHITSUKA. (Prime::EratosthenesSieve): added. * lib/.document (prime.rb): added * lib/README (prime.rb): added * test/test_prime.rb: added. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@19095 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- ChangeLog | 43 ++++++ lib/.document | 1 + lib/README | 1 + lib/mathn.rb | 96 +----------- lib/prime.rb | 446 +++++++++++++++++++++++++++++++++++++++++++++++++++++ test/test_prime.rb | 126 +++++++++++++++ 6 files changed, 618 insertions(+), 95 deletions(-) create mode 100644 lib/prime.rb create mode 100644 test/test_prime.rb diff --git a/ChangeLog b/ChangeLog index 6790515ea8..1ca202c77c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,46 @@ +Wed Sep 3 22:31:11 2008 Yuki Sonoda (Yugui) + + * lib/mathn.rb (Integer): moved into prime.rb. + (Prime): ditto. + + * lib/prime.rb (Integer): moved from mathn.rb. + (Integer.each_prime): added. + (Integer#prime?): added. + (Prime): moved from mathn.rb. + Its implmentation was rewritten. see [ruby-dev:35863]. + And patched by Keiju ISHITSUKA , + see [ruby-dev:36128]. + (Prime.new): obsolete. + (Prime.instance): added. + (Prime.each): added. + (Prime.int_from_prime_division): added. + (Prime.prime_division): added. + (Prime.prime?): added. + Patch by TOYOFUKU Chikanobu + in [ruby-dev:36067]. + (Prime.cache): removed. + (Prime.primes): removed. + (Prime.primes_so_far): removed. + (Prime#int_from_prime_division): added. + (Prime#prime_division): added. + (Prime#prime?): added. + (Prime#primes): removed. + (Prime#primes_so_far): removed. + (Prime::PseudoPrmeGenerator): added. + (Prime::EratosthenesGenerator): added. + (Prime::TrialDivisionGenerator): added. + (Prime::Generator23): added. + (Prime::TrialDivision): added. + Extracted from the previous implementation of Prime + by Keiju ISHITSUKA. + (Prime::EratosthenesSieve): added. + + * lib/.document (prime.rb): added + + * lib/README (prime.rb): added + + * test/test_prime.rb: added. + Wed Sep 3 21:49:00 2008 David A. Black * lib/scanf.rb: fixed bug involving matching literal '[' diff --git a/lib/.document b/lib/.document index f6b301327f..d3c4a1369b 100644 --- a/lib/.document +++ b/lib/.document @@ -59,6 +59,7 @@ pathname.rb ping.rb pp.rb prettyprint.rb +prime.rb profile.rb profiler.rb pstore.rb diff --git a/lib/README b/lib/README index 074741f953..1132755cf5 100644 --- a/lib/README +++ b/lib/README @@ -42,6 +42,7 @@ parsedate.rb parses date string (obsolete) pathname.rb Object-Oriented Pathname Class pp.rb pretty print objects prettyprint.rb pretty printing algorithm +prime.rb prime numbers and factorization profile.rb runs ruby profiler profiler.rb ruby profiler module pstore.rb persistent object strage using marshal diff --git a/lib/mathn.rb b/lib/mathn.rb index ce1acc927f..2af2b83da3 100644 --- a/lib/mathn.rb +++ b/lib/mathn.rb @@ -12,101 +12,7 @@ require "complex.rb" require "rational.rb" require "matrix.rb" - -class Integer - - def Integer.from_prime_division(pd) - value = 1 - for prime, index in pd - value *= prime**index - end - value - end - - def prime_division - raise ZeroDivisionError if self == 0 - ps = Prime.new - value = self - pv = [] - for prime in ps - count = 0 - while (value1, mod = value.divmod(prime) - mod) == 0 - value = value1 - count += 1 - end - if count != 0 - pv.push [prime, count] - end - break if prime * prime >= value - end - if value > 1 - pv.push [value, 1] - end - return pv - end -end - -class Prime - include Enumerable - # These are included as class variables to cache them for later uses. If memory - # usage is a problem, they can be put in Prime#initialize as instance variables. - - # There must be no primes between @@primes[-1] and @@next_to_check. - @@primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101] - # @@next_to_check % 6 must be 1. - @@next_to_check = 103 # @@primes[-1] - @@primes[-1] % 6 + 7 - @@ulticheck_index = 3 # @@primes.index(@@primes.reverse.find {|n| - # n < Math.sqrt(@@next_to_check) }) - @@ulticheck_next_squared = 121 # @@primes[@@ulticheck_index + 1] ** 2 - - class << self - # Return the prime cache. - def cache - return @@primes - end - alias primes cache - alias primes_so_far cache - end - - def initialize - @index = -1 - end - - # Return primes given by this instance so far. - def primes - return @@primes[0, @index + 1] - end - alias primes_so_far primes - - def succ - @index += 1 - while @index >= @@primes.length - # Only check for prime factors up to the square root of the potential primes, - # but without the performance hit of an actual square root calculation. - if @@next_to_check + 4 > @@ulticheck_next_squared - @@ulticheck_index += 1 - @@ulticheck_next_squared = @@primes.at(@@ulticheck_index + 1) ** 2 - end - # Only check numbers congruent to one and five, modulo six. All others - # are divisible by two or three. This also allows us to skip checking against - # two and three. - @@primes.push @@next_to_check if @@primes[2..@@ulticheck_index].find {|prime| @@next_to_check % prime == 0 }.nil? - @@next_to_check += 4 - @@primes.push @@next_to_check if @@primes[2..@@ulticheck_index].find {|prime| @@next_to_check % prime == 0 }.nil? - @@next_to_check += 2 - end - return @@primes[@index] - end - alias next succ - - def each - return to_enum(:each) unless block_given? - loop do - yield succ - end - end -end +require "prime.rb" class Fixnum remove_method :/ diff --git a/lib/prime.rb b/lib/prime.rb new file mode 100644 index 0000000000..be1c8b42a0 --- /dev/null +++ b/lib/prime.rb @@ -0,0 +1,446 @@ +# +# = prime.rb +# +# Prime numbers and factorization library. +# +# Copyright:: +# Copyright (c) 1998-2008 Keiju ISHITSUKA(SHL Japan Inc.) +# Copyright (c) 2008 Yuki Sonoda (Yugui) +# +# Documentation:: +# Yuki Sonoda +# + +require "singleton" +require "forwardable" + +class Integer + # Re-composes a prime factorization and returns the product. + # + # See Prime#int_from_prime_division for more details. + def Integer.from_prime_division(pd) + Prime.int_from_prime_division(pd) + end + + # Returns the factorization of +self+. + # + # See Prime#prime_division for more details. + def prime_division(generator = Prime::Generator23.new) + Prime.prime_division(self, generator) + end + + # Returns true if +self+ is a prime number, false for a composite. + def prime? + Prime.prime?(self) + end + + # Iterates the given block over all prime numbers. + # + # See +Prime+#each for more details. + def Integer.each_prime(ubound, &block) # :yields: prime + Prime.each(ubound, &block) + end +end + +# +# The set of all prime numbers. +# +# == Example +# Prime.each(100) do |prime| +# p prime #=> 2, 3, 5, 7, 11, ...., 97 +# end +# +# == Retrieving the instance +# +Prime+.new is obsolete. Now +Prime+ has the default instance and you can +# access it as +Prime+.instance. +# +# For convenience, each instance method of +Prime+.instance can be accessed +# as a class method of +Prime+. +# +# e.g. +# Prime.instance.prime?(2) #=> true +# Prime.prime?(2) #=> true +# +# == Generators +# A "generator" provides an implementation of enumerating pseudo-prime +# numbers and it remembers the position of enumeration and upper bound. +# Futhermore, it is a external iterator of prime enumeration which is +# compatible to an Enumerator. +# +# +Prime+::+PseudoPrimeGenerator+ is the base class for generators. +# There are few implementations of generator. +# +# [+Prime+::+EratosthenesGenerator+] +# Uses eratosthenes's sieve. +# [+Prime+::+TrialDivisionGenerator+] +# Uses the trial division method. +# [+Prime+::+Generator23+] +# Generates all positive integers which is not divided by 2 nor 3. +# This sequence is very bad as a pseudo-prime sequence. But this +# is faster and uses much less memory than other generators. So, +# it is suitable for factorizing an integer which is not large but +# has many prime factors. e.g. for Prime#prime? . +class Prime + include Enumerable + @the_instance = Prime.new + + # obsolete. Use +Prime+::+instance+ or class methods of +Prime+. + def initialize + @generator = EratosthenesGenerator.new + extend OldCompatibility + warn "Prime::new is obsolete. use Prime::instance or class methods of Prime." + end + + module OldCompatibility + def succ + @generator.succ + end + alias next succ + + def each(&block) + loop do + yield succ + end + end + end + + class< 12 + def int_from_prime_division(pd) + pd.inject(1){|value, (prime, index)| + value *= prime**index + } + end + + # Returns the factorization of +value+. + # + # == Parameters + # +value+:: An arbitrary integer. + # +generator+:: Optional. A pseudo-prime generator. + # +generator+.succ must return the next + # pseudo-prime number in the ascendent + # order. It must generate all prime numbers, + # but may generate non prime numbers. + # + # === Exceptions + # +ZeroDivisionError+:: when +value+ is zero. + # + # == Example + # For an arbitrary integer + # n = p_1**e_1 * p_2**e_2 * .... * p_n**e_n, + # prime_division(n) returns + # [[p_1, e_1], [p_2, e_2], ...., [p_n, e_n]]. + # + # Prime.prime_division(12) #=> [[2,2], [3,1]] + # + def prime_division(value, generator= Prime::Generator23.new) + raise ZeroDivisionError if value == 0 + pv = [] + for prime in generator + count = 0 + while (value1, mod = value.divmod(prime) + mod) == 0 + value = value1 + count += 1 + end + if count != 0 + pv.push [prime, count] + end + break if value1 <= prime + end + if value > 1 + pv.push [value, 1] + end + return pv + end + + # An abstract class for enumerating pseudo-prime numbers. + # + # Concrete subclasses should override succ, next, rewind. + class PseudoPrimeGenerator + include Enumerable + + def initialize(ubound = nil) + @ubound = ubound + end + + def upper_bound=(ubound) + @ubound = ubound + end + def upper_bound + @ubound + end + + # returns the next pseudo-prime number, and move the internal + # position forward. + # + # +PseudoPrimeGenerator+#succ raises +NotImplementedError+. + def succ + raise NotImplementedError, "need to define `succ'" + end + + # alias of +succ+. + def next + raise NotImplementedError, "need to define `next'" + end + + # Rewinds the internal position for enumeration. + # + # See +Enumerator+#rewind. + def rewind + raise NotImplementedError, "need to define `rewind'" + end + + # Iterates the given block for each prime numbers. + # +ubound+:: + def each(&block) + return self.dup unless block + if @ubound + loop do + p = succ + break if p > @ubound + block.call p + end + else + loop do + block.call succ + end + end + end + + # see +Enumerator+#with_index. + alias with_index each_with_index + + # see +Enumerator+#with_object. + def with_object(obj) + return enum_for(:with_object) unless block_given? + each do |prime| + yield prime, obj + end + end + end + + # An implementation of +PseudoPrimeGenerator+. + # + # Uses +EratosthenesSieve+. + class EratosthenesGenerator < PseudoPrimeGenerator + def initialize + @last_prime = nil + end + + def succ + @last_prime = @last_prime ? EratosthenesSieve.instance.next_to(@last_prime) : 2 + end + def rewind + initialize + end + alias next succ + end + + # An implementation of +PseudoPrimeGenerator+ which uses + # a prime table generated by trial division. + class TrialDivisionGenerator= @primes.length + # Only check for prime factors up to the square root of the potential primes, + # but without the performance hit of an actual square root calculation. + if @next_to_check + 4 > @ulticheck_next_squared + @ulticheck_index += 1 + @ulticheck_next_squared = @primes.at(@ulticheck_index + 1) ** 2 + end + # Only check numbers congruent to one and five, modulo six. All others + + # are divisible by two or three. This also allows us to skip checking against + # two and three. + @primes.push @next_to_check if @primes[2..@ulticheck_index].find {|prime| @next_to_check % prime == 0 }.nil? + @next_to_check += 4 + @primes.push @next_to_check if @primes[2..@ulticheck_index].find {|prime| @next_to_check % prime == 0 }.nil? + @next_to_check += 2 + end + return @primes[index] + end + end + + # An implementation of eratosthenes's sieve + class EratosthenesSieve + include Singleton + + def initialize # :nodoc: + # bitmap for odd prime numbers less than 256. + # For an arbitrary odd number n, @table[i][j] is 1 when n is prime where i,j = n.divmod(32) . + @table = [0xcb6e, 0x64b4, 0x129a, 0x816d, 0x4c32, 0x864a, 0x820d, 0x2196] + end + + # returns the least odd prime number which is greater than +n+. + def next_to(n) + n = (n-1).div(2)*2+3 # the next odd number of given n + i,j = n.divmod(32) + loop do + extend_table until @table.length > i + if !@table[i].zero? + (j...32).step(2) do |j| + return 32*i+j if !@table[i][j.div(2)].zero? + end + end + i += 1; j = 1 + end + end + + private + def extend_table + orig_len = @table.length + new_len = [orig_len**2, orig_len+256].min + lbound = orig_len*32 + ubound = new_len*32 + @table.fill(0xFFFF, orig_len...new_len) + (3..Integer(Math.sqrt(ubound))).step(2) do |p| + i, j = p.divmod(32) + next if @table[i][j.div(2)].zero? + + start = (lbound.div(2*p)*2+1)*p # odd multiple of p which is greater than or equal to lbound + (start...ubound).step(2*p) do |n| + i, j = n.divmod(32) + @table[i] &= 0xFFFF ^ (1<<(j.div(2))) + end + end + end + end +end diff --git a/test/test_prime.rb b/test/test_prime.rb new file mode 100644 index 0000000000..aca5109689 --- /dev/null +++ b/test/test_prime.rb @@ -0,0 +1,126 @@ +require 'test/unit' +require 'prime' +require 'stringio' + +class TestPrime < Test::Unit::TestCase + # The first 100 prime numbers + PRIMES = [ + 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, + 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, + 89, 97, 101, 103, 107, 109, 113, 127, 131, + 137, 139, 149, 151, 157, 163, 167, 173, 179, + 181, 191, 193, 197, 199, 211, 223, 227, 229, + 233, 239, 241, 251, 257, 263, 269, 271, 277, + 281, 283, 293, 307, 311, 313, 317, 331, 337, + 347, 349, 353, 359, 367, 373, 379, 383, 389, + 397, 401, 409, 419, 421, 431, 433, 439, 443, + 449, 457, 461, 463, 467, 479, 487, 491, 499, + 503, 509, 521, 523, 541, + ] + def test_each + primes = [] + Prime.each do |p| + break if p > 541 + primes << p + end + assert_equal PRIMES, primes + end + + def test_each_by_prime_number_theorem + 3.upto(15) do |i| + max = 2**i + primes = [] + Prime.each do |p| + break if p >= max + primes << p + end + + # Prime number theorem + assert primes.length >= max/Math.log(max) + delta = 0.05 + li = (2..max).step(delta).inject(0){|sum,x| sum + delta/Math.log(x)} + assert primes.length <= li + end + end + + def test_each_without_block + enum = Prime.each + assert enum.respond_to?(:each) + assert enum.kind_of?(Enumerable) + assert enum.respond_to?(:with_index) + assert enum.respond_to?(:next) + assert enum.respond_to?(:succ) + assert enum.respond_to?(:rewind) + end + + def test_new + buf = StringIO.new('', 'w') + orig, $stderr = $stderr, buf + + enum = Prime.new + assert !buf.string.empty? + $stderr = orig + + assert enum.respond_to?(:each) + assert enum.kind_of?(Enumerable) + assert enum.respond_to?(:succ) + + assert Prime === enum + ensure + $stderr = orig + end + + def test_enumerator_succ + enum = Prime.each + assert_equal PRIMES[0, 50], 50.times.map{ enum.succ } + assert_equal PRIMES[50, 50], 50.times.map{ enum.succ } + enum.rewind + assert_equal PRIMES[0, 100], 100.times.map{ enum.succ } + end + + def test_enumerator_with_index + enum = Prime.each + last = -1 + enum.with_index do |p,i| + break if i >= 100 + assert_equal last+1, i + assert_equal PRIMES[i], p + last = i + end + end + + class TestInteger < Test::Unit::TestCase + def test_prime_division + pd = PRIMES.inject(&:*).prime_division + assert_equal PRIMES.map{|p| [p, 1]}, pd + end + + def test_from_prime_division + assert_equal PRIMES.inject(&:*), Integer.from_prime_division(PRIMES.map{|p| [p,1]}) + end + + def test_prime? + # small primes + assert 2.prime? + assert 3.prime? + + # squared prime + assert !4.prime? + assert !9.prime? + + # mersenne numbers + assert (2**31-1).prime? + assert !(2**32-1).prime? + + # fermat numbers + assert (2**(2**4)+1).prime? + assert !(2**(2**5)+1).prime? # Euler! + + # large composite + assert !((2**13-1) * (2**17-1)).prime? + + # factorial + assert !(2...100).inject(&:*).prime? + end + end +end -- cgit v1.2.3