class PrimeTable
It’s a pretty simple program. I considered making multiple classes, but it’s not necessary.
Constants
- PRIMES
- VERSION
Attributes
We’re going to want to get and set things from inside multiple methods, so…
We’re going to want to get and set things from inside multiple methods, so…
Public Class Methods
# File lib/primetable.rb, line 22 def initialize(first=1, count=10, prime_method=:calc, suppress_output=false) # This is only for testing, I'm unlikely to implement a command-line flag for this unless suppress_output puts "PrimeTable is running..." end # Ye olde instance variables @primes = init_primes(prime_method,first,count) @table = build_data(@primes) # I added this suppress_output so I could run certain tests without a huge, admittly # ugly, table clobbering my nice green passing specs unless suppress_output display_table(@table) end end
Public Instance Methods
# File lib/primetable.rb, line 163 def build_data(primes) # To make this code simpler we act as if our first prime is 1 so that the matrix includes # our header row and column of primes (multiplied by 1)...we remove the corner 1 later primes.unshift(1) # An empty two-dimensional array to act as the data model for our table table = Array.new(primes.length) { Array.new(primes.length) } # We populate the array-of-arrays with the products-of-primes ;-) primes.each_with_index{|outerprime,i| primes.each_with_index{|innerprime,y| table[i][y] = outerprime*innerprime } } # Here we remove the corner 1 which was added as a side-effect of the matrix generation primes.shift(1) table[0][0] = nil # And send the table data to display (or, in theory, whoever called us) table end
Calculates the primes using basic math. This can be slow for higher primes or higher counts. TODO: Include note on computational complexity here: O(n*sqrt(n)), I think TODO: Include note on average run time using this method.
# File lib/primetable.rb, line 150 def calc_primes(first, count) # One nice thing about this is that I can easily set a timeout, so if someone asks us to run # some astronomical prime, we won't seize up the CPU forever. 7000ms is arbitrary. calc_primes_js = V8::Context.new timeout: 7000 File.open("#{ROOT_DIR}/js/prime.js") do |file| calc_primes_js.eval(file) end primes_js = calc_primes_js.eval("generatePrimes(#{first},#{count})") YAML::load("[#{primes_js}]") end
Use Formatador to make this display nicely.
# File lib/primetable.rb, line 189 def display_table(table) columns = table.shift table_data = table.map do |products| row_hash = Hash.new columns.each_with_index do |label, index| key = (columns[index] || '') row_hash[key] = products[index] end row_hash end # Note that we override the default string sort by passing a block with an integer sort Formatador.display_table(table_data){ |x, y| x.to_i <=> y.to_i } end
# File lib/primetable.rb, line 76 def fast_primes(first,count) # Since we allow people to pass their best guess for first and use the next highest prime while PrimeTable::PRIMES.index(first) === nil first = first + 1 end PrimeTable::PRIMES.slice(PrimeTable::PRIMES.index(first),count) end
One way or another, we need to get an array of consecutive prime numbers.
# File lib/primetable.rb, line 61 def init_primes(prime_method, first, count) # Of course, we calculate the primes by default. But having three methods is not only # fancy; it's also comes in handy for testing that our calculated values are correct. case prime_method when :fast # Using precalculated values from code. 8-17ms, mode 13ms on benchmark (2,10) run, 14-24ms/20ms on (200000,12) fast_primes(first,count) when :load # Using precalculated values from file. 45-256ms, mode 50ms on benchmark (2,10) run, 669-913ms/681ms on (200000,12) load_primes(first,count) when :calc # Using JS generated values. 14-84ms, Mode 18ms on benchmark (2,10) run, 17-25ms/21ms on (200000,12) calc_primes(first,count) end # case prime_method end
Loads precalculated primes from a data file. The file is formatted with 10 primes per line. Each line is enclosed in square brackets, and the primes are comma-delimited. Thus, it’s trivial to read each line in as an array of ten primes. Please note that this file is 995K and contains 13413 lines. You don’t want to just slurp this file. That would be rude. Instead we read lines one at a time and only keep what we need. Also note the highest prime in this file is currently 2000003, although conceivably we could use the :calc method and extend it.
# File lib/primetable.rb, line 92 def load_primes(first, count) # An array to keep our prime numbers in, right? primes = [] # Previously, the 'first' argument was an index, now it's a best guess for the first prime, # so we have to find the index in order to find the line number if first == 1 first = 2 end if first == 2 wb_before = "" else wb_before = "," end if first >= 2000003 wb_after = "" else wb_after = "," end grep_result = [] while grep_result == [] grep_result = `grep -n -e "#{wb_before}#{first}#{wb_after}" #{ROOT_DIR}/data/prime.dat`.split(":") if grep_result == [] first = first + 1 end end first_line = grep_result[0].to_i-1 first_index = YAML::load(grep_result[1]).index(first) last_line = (((first_line)*10+first_index+count)/10).ceil # Again, we only have the first 134130 primes in here right now (on 13413 lines) if last_line > 13413 throw "We only have the first 134130 primes in the data file right now. Please pass -c to use calculated primes." end # Here we read in the file one line at a time. Yes I looked for a faster way. Try :fast (-f). # 1) We don't want to slurp the whole file...what if the file was even larger? Not cool. # 2) IO.readlines gets the file one line at a time, so we need a line number, not an index. IO.readlines("#{ROOT_DIR}/data/prime.dat").each_with_index{|line,ln| # When we find the lines we want, we concatenate them into a single array. We get extra. if (ln >= first_line and ln <= last_line) primes.concat(YAML::load(line)) end } # The extra is trimmed off in this step. We know what we want. Just this, thanks! primes.slice!(first_index,count) end