# ruby/ruby

### Subversion checkout URL

You can clone with
or
.
Fetching contributors…
Cannot retrieve contributors at this time
510 lines (463 sloc) 14 KB
 # # = 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 # # Prime is Enumerable: # # Prime.first 5 # => [2, 3, 5, 7, 11] # # == 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 class << self extend Forwardable include Enumerable # Returns the default instance of Prime. def instance; @the_instance end def method_added(method) # :nodoc: (class<< self;self;end).def_delegator :instance, method end end # Iterates the given block over all prime numbers. # # == Parameters # # +ubound+:: # Optional. An arbitrary positive number. # The upper bound of enumeration. The method enumerates # prime numbers infinitely if +ubound+ is nil. # +generator+:: # Optional. An implementation of pseudo-prime generator. # # == Return value # # An evaluated value of the given block at the last time. # Or an enumerator which is compatible to an +Enumerator+ # if no block given. # # == Description # # Calls +block+ once for each prime number, passing the prime as # a parameter. # # +ubound+:: # Upper bound of prime numbers. The iterator stops after # yields all prime numbers p <= +ubound+. # # == Note # # +Prime+.+new+ returns a object extended by +Prime+::+OldCompatibility+ # in order to compatibility to Ruby 1.8, and +Prime+#each is overwritten # by +Prime+::+OldCompatibility+#+each+. # # +Prime+.+new+ is now obsolete. Use +Prime+.+instance+.+each+ or simply # +Prime+.+each+. def each(ubound = nil, generator = EratosthenesGenerator.new, &block) generator.upper_bound = ubound generator.each(&block) end # Returns true if +value+ is prime, false for a composite. # # == Parameters # # +value+:: an arbitrary integer to be checked. # +generator+:: optional. A pseudo-prime generator. def prime?(value, generator = Prime::Generator23.new) value = -value if value < 0 return false if value < 2 for num in generator q,r = value.divmod num return true if q < num return false if r == 0 end end # Re-composes a prime factorization and returns the product. # # == Parameters # +pd+:: Array of pairs of integers. The each internal # pair consists of a prime number -- a prime factor -- # and a natural number -- an exponent. # # == Example # For [[p_1, e_1], [p_2, e_2], ...., [p_n, e_n]], it returns: # # p_1**e_1 * p_2**e_2 * .... * p_n**e_n. # # Prime.int_from_prime_division([[2,2], [3,1]]) #=> 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 if value < 0 value = -value pv = [[-1, 1]] else pv = [] end 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. def each(&block) return self.dup unless block if @ubound last_value = nil loop do prime = succ break last_value if prime > @ubound last_value = block.call(prime) 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 super 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 # Internal use. An implementation of eratosthenes's sieve class EratosthenesSieve include Singleton BITS_PER_ENTRY = 16 # each entry is a set of 16-bits in a Fixnum NUMS_PER_ENTRY = BITS_PER_ENTRY * 2 # twiced because even numbers are omitted ENTRIES_PER_TABLE = 8 NUMS_PER_TABLE = NUMS_PER_ENTRY * ENTRIES_PER_TABLE FILLED_ENTRY = (1 << NUMS_PER_ENTRY) - 1 def initialize # :nodoc: # bitmap for odd prime numbers less than 256. # For an arbitrary odd number n, @tables[i][j][k] is # * 1 if n is prime, # * 0 if n is composite, # where i,j,k = indices(n) @tables = [[0xcb6e, 0x64b4, 0x129a, 0x816d, 0x4c32, 0x864a, 0x820d, 0x2196].freeze] 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 to given n table_index, integer_index, bit_index = indices(n) loop do extend_table until @tables.length > table_index for j in integer_index...ENTRIES_PER_TABLE if !@tables[table_index][j].zero? for k in bit_index...BITS_PER_ENTRY return NUMS_PER_TABLE*table_index + NUMS_PER_ENTRY*j + 2*k+1 if !@tables[table_index][j][k].zero? end end bit_index = 0 end table_index += 1; integer_index = 0 end end private # for an odd number +n+, returns (i, j, k) such that @tables[i][j][k] represents primarity of the number def indices(n) # binary digits of n: |0|1|2|3|4|5|6|7|8|9|10|11|.... # indices: |-| k | j | i # because of NUMS_PER_ENTRY, NUMS_PER_TABLE k = (n & 0b00011111) >> 1 j = (n & 0b11100000) >> 5 i = n >> 8 return i, j, k end def extend_table lbound = NUMS_PER_TABLE * @tables.length ubound = lbound + NUMS_PER_TABLE new_table = [FILLED_ENTRY] * ENTRIES_PER_TABLE # which represents primarity in lbound...ubound (3..Integer(Math.sqrt(ubound))).step(2) do |p| i, j, k = indices(p) next if @tables[i][j][k].zero? start = (lbound.div(p)+1)*p # least multiple of p which is >= lbound start += p if start.even? (start...ubound).step(2*p) do |n| _, j, k = indices(n) new_table[j] &= FILLED_ENTRY^(1<
Something went wrong with that request. Please try again.