prime_products 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 33ac5620ba1e8f2ad6673b6a0594395d0228b8f79e3b576a0ef333474169bd06
4
- data.tar.gz: 9b10d62cc31781174c261eb0496dd270b287613ab1b21aa6184d0913851e8269
3
+ metadata.gz: 96a7de0da10c68e17d6dcd7e32e3b1aa7e17e340e0eba32537a9796d9316ca5d
4
+ data.tar.gz: e6c6dd590d1d252ed5d475c3d0af092e20218a1fc3d042a0d31a5f41c751225c
5
5
  SHA512:
6
- metadata.gz: 11d1a587e00911b5c2199eea91465b6cf88d1159ee1474f0b09f8ae2fac9981dac0b4aedebc851d45b9dfb42ed9ed0b244ca2757102d51881f2e80f5c3b66ed0
7
- data.tar.gz: f3f2cd5387c6d78787322fe0629023e7a9eb2f90081362d0d2577023611e25d190bb1cadaf646e8f9f76b2222920934a01c45bd8cfda23c45b50671e82832c5c
6
+ metadata.gz: 1e45aa1bb267f93246a6b07064331fbcc0c8e773051ad1da973f2a25137bbf35b12fb65faced2577736e2bbe68e2f2031335977dafdf6d4cb8b4473610f2e10a
7
+ data.tar.gz: 2c7904556a01e57d85d1c178879ca6d4c444348cb9ad74a9747b52ac235b99923b654f8904f5147d7e28108e49fb1bf8ff15bd6f91e7d5d490eccb0f3777dd0b
@@ -1,3 +1,7 @@
1
+ # v0.2.0 (26 Nov 2018)
2
+
3
+ * Use the Sieve of Eratosthenes method to find prime numbers
4
+
1
5
  # v0.1.1 (25 Nov 2018)
2
6
 
3
7
  * Rename `PrimeProducts::ProductsTable` to `PrimeProducts::MultiplicationTable`
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- prime_products (0.1.1)
4
+ prime_products (0.2.0)
5
5
  hanami-cli (~> 0.3.0)
6
6
  immutable-ruby (~> 0.0.3)
7
7
  terminal-table (~> 1.8.0)
data/README.md CHANGED
@@ -47,24 +47,29 @@ It finds the number of primes to use, looking at `ARGV` and defaulting to `CLI::
47
47
 
48
48
  It then calls `PrimeProducts.generate_table` with the `number_of_primes`, and writes the result to the output,
49
49
 
50
- `PrimeProducts.generate_table` finds the requested number of prime numbers using `PrimeProducts::PrimeNumbers.first`, and then asks `PrimeProducts::MultiplicationTable.generate` to generate a multiplication table from them.
50
+ `PrimeProducts.generate_table` finds the requested number of prime numbers using `PrimeProducts::PrimeNumbers::SieveOfEratosthenes.first`, and then asks `PrimeProducts::MultiplicationTable.generate` to generate a multiplication table from them.
51
51
 
52
- `PrimeProducts::PrimeNumbers.first` uses a simple, naive but sufficiently performant method of finding the primes - see the "Benchmarking" section below - and returns them in an `Immutable::SortedSet` to avoid the danger of accidental mutation.
52
+ `PrimeProducts::PrimeNumbers::SieveOfEratosthenes.first` uses the performant Sieve of Eratosthenes method for finding primes - see the "Benchmarking" section below - and returns them in an `Immutable::SortedSet` to avoid the danger of accidental mutation.
53
53
 
54
54
  ## Benchmarking
55
55
 
56
- This gem uses a naive method of finding the first *n* primes which does not scale especially well. However, this seems reasonable given that the primes will be displayed on-screen, and so it is unlikely you will want to see a large number at once.
56
+ This gem uses the performant Sieve of Eratosthenes method of finding prime numbers (`PrimeProducts::PrimeNumbers::SieveOfEratosthenes`):
57
57
 
58
- Despite being a naive solution, this is still moderately performant. On my machine:
58
+ * finding the first 10 takes ~0.0001s
59
+ * finding the first 100 takes ~0.0006s
60
+ * finding the first 1000 takes ~0.007s
61
+ * finding the first 10000 takes ~0.1s
59
62
 
60
- * finding the first 10 takes ~0.00004s
61
- * finding the first 100 takes ~0.001s
62
- * finding the first 1000 takes ~0.2s
63
- * finding the first 10000 takes ~31s
63
+ As is evident, it scales well for large numbers of primes.
64
64
 
65
- It is unlikely that you would want a table of more than 1000, which only takes ~0.2 seconds. If we wanted to use these numbers elsewhere, and needed more, then I would optimise this code.
65
+ The gem also includes, but doesn't use, a naive method of generating primes (`PrimeProducts::PrimeNumbers::Naive`). It is much less performant - but __this trade-off here might be worth making for simpler code, given that for the use case of displaying multiplication tables, you would only ever want to generate a relatively small set of prime numbers__:
66
66
 
67
- You can see full benchmark results, comparing the Ruby standard library's implementation (`Prime.first`) to mine, by running `ruby benchmarks/prime_numbers.rb`.
67
+ * finding the first 10 takes ~0.0001s
68
+ * finding the first 100 takes ~0.003s
69
+ * finding the first 1000 takes ~0.35s
70
+ * finding the first 10000 takes ~44s
71
+
72
+ You can see full benchmark results, comparing the Ruby standard library's implementation (`Prime.first`) to these implementations, by running `ruby benchmarks/prime_numbers.rb`.
68
73
 
69
74
  ## Development
70
75
 
@@ -2,11 +2,13 @@
2
2
 
3
3
  require "benchmark"
4
4
  require "prime"
5
- require_relative "../lib/prime_products/prime_numbers"
5
+ require_relative "../lib/prime_products/prime_numbers/naive"
6
+ require_relative "../lib/prime_products/prime_numbers/sieve_of_eratosthenes"
6
7
 
7
8
  Benchmark.bm do |b|
8
9
  [10, 100, 1000, 10_000].each do |n|
9
- b.report("naive_#{n}") { PrimeProducts::PrimeNumbers.first(n) }
10
+ b.report("naive_#{n}") { PrimeProducts::PrimeNumbers::Naive.first(n) }
11
+ b.report("sieve_#{n}") { PrimeProducts::PrimeNumbers::SieveOfEratosthenes.first(n) }
10
12
  b.report("stdlib_#{n}") { Prime.first(n) }
11
13
  end
12
14
  end
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "prime_products/cli"
4
- require "prime_products/prime_numbers"
4
+ require "prime_products/prime_numbers/naive"
5
+ require "prime_products/prime_numbers/sieve_of_eratosthenes"
5
6
  require "prime_products/multiplication_table"
6
7
  require "prime_products/version"
7
8
 
@@ -24,7 +25,7 @@ module PrimeProducts
24
25
  # prime numbers, starting from 0, you want to the multiplication table of
25
26
  # @return [String] the generated table
26
27
  def self.generate_table(number_of_primes:)
27
- prime_numbers = PrimeNumbers.first(number_of_primes)
28
+ prime_numbers = PrimeNumbers::Naive.first(number_of_primes)
28
29
  MultiplicationTable.generate(prime_numbers)
29
30
  end
30
31
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "immutable"
4
+
5
+ module PrimeProducts
6
+ module PrimeNumbers
7
+ module Naive
8
+ INFINITY = (1.0 / 0)
9
+
10
+ # Finds the first `n` prime numbers using a naive, but easy to understand, method.
11
+ # Alternative solutions are easy to find and implement.
12
+ #
13
+ # However, I am opting to stick with a simpler more friendly option to keep
14
+ # complexity low, given that __we will be displaying results on a screen__, making
15
+ # it unlikely that you will show huge quantities.
16
+ #
17
+ # Despite being a naive solution, this is still moderately performant. On my
18
+ # machine:
19
+ #
20
+ # * finding the first 10 takes ~0.00004s
21
+ # * finding the first 100 takes ~0.001s
22
+ # * finding the first 1000 takes ~0.2s
23
+ # * finding the first 10000 takes ~31s
24
+ #
25
+ # It is unlikely that you would want a table of more than 1000, which only takes
26
+ # ~0.2 seconds. If we wanted to use these numbers elsewhere, and needed more,
27
+ # then I would optimise this code.
28
+ #
29
+ # @param number_of_primes [Integer] the number of prime numbers, starting from 0,
30
+ # you want to find
31
+ # @return [Immutable::SortedSet<Integer>] the first `n` prime numbers
32
+ def self.first(number_of_primes)
33
+ # In Ruby 2.6, you can generate a Range up to infinity with `(2..)` 😍
34
+ primes = (2..INFINITY).
35
+ lazy.
36
+ reject { |i| (2...i).any? { |divisor| (i % divisor).zero? } }.
37
+ take(number_of_primes).
38
+ to_a
39
+
40
+ Immutable::SortedSet[*primes]
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "immutable"
4
+
5
+ module PrimeProducts
6
+ module PrimeNumbers
7
+ module SieveOfEratosthenes
8
+ # Finds the first `n` prime numbers using the performant Sieve of Eratosthenes
9
+ # method.
10
+ #
11
+ # See `benchmarks/prime_numbers.rb` - it performs well, but as not as well as
12
+ # the solution in Ruby's stdlib 😭 But at least it's written in Ruby (not C) and is
13
+ # fairly easy to understand.
14
+ #
15
+ # @param number_of_primes [Integer] the number of prime numbers, starting from 0,
16
+ # you want to find
17
+ # @return [Immutable::SortedSet<Integer>] the first `n` prime numbers
18
+ def self.first(number_of_primes)
19
+ estimated_max = number_of_primes * Math.log(number_of_primes * number_of_primes)
20
+ Immutable::SortedSet[*upto(estimated_max).first(number_of_primes)]
21
+ end
22
+
23
+ # Finds all prime numbers up to `maximum` using the performant Sieve of
24
+ # Eratosthenes method.
25
+ #
26
+ # @param maximum [Integer] the number you want to find primes up to
27
+ # @return [Array<Integer>] all of the prime numbers up to `maximum`
28
+ def self.upto(maximum)
29
+ # All numbers *might* be primes until we start ruling them out
30
+ primes = (0..maximum).to_a
31
+
32
+ # Zero and one aren't considered prime numbers
33
+ primes[0] = primes[1] = nil
34
+
35
+ primes.each do |n|
36
+ # Skip if this number has already been removed when stepping through
37
+ # multiples
38
+ next unless n
39
+
40
+ break if n * n > maximum
41
+
42
+ # Remove multiples of the number we're looking at - they're not primes
43
+ (n * n).step(maximum, n) { |i| primes[i] = nil }
44
+ end
45
+
46
+ primes.compact
47
+ end
48
+ end
49
+ end
50
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PrimeProducts
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prime_products
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tim Rogers
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-11-25 00:00:00.000000000 Z
11
+ date: 2018-11-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: hanami-cli
@@ -161,7 +161,8 @@ files:
161
161
  - lib/prime_products.rb
162
162
  - lib/prime_products/cli.rb
163
163
  - lib/prime_products/multiplication_table.rb
164
- - lib/prime_products/prime_numbers.rb
164
+ - lib/prime_products/prime_numbers/naive.rb
165
+ - lib/prime_products/prime_numbers/sieve_of_eratosthenes.rb
165
166
  - lib/prime_products/version.rb
166
167
  - prime_products.gemspec
167
168
  homepage:
@@ -1,40 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "immutable"
4
-
5
- module PrimeProducts
6
- module PrimeNumbers
7
- INFINITY = (1.0 / 0)
8
-
9
- # Finds the first `n` prime numbers using a naive, but easy to understand, method.
10
- # Alternative solutions are easy to find and implement.
11
- #
12
- # However, I am opting to stick with a simpler more friendly option to keep
13
- # complexity low, given that __we will be displaying results on a screen__, making it
14
- # unlikely that you will show huge quantities.
15
- #
16
- # Despite being a naive solution, this is still moderately performant. On my machine:
17
- #
18
- # * finding the first 10 takes ~0.00004s
19
- # * finding the first 100 takes ~0.001s
20
- # * finding the first 1000 takes ~0.2s
21
- # * finding the first 10000 takes ~31s
22
- #
23
- # It is unlikely that you would want a table of more than 1000, which only takes
24
- # ~0.2 seconds. If we wanted to use these numbers elsewhere, and needed more, then I
25
- # would optimise this code.
26
- #
27
- # @param n [Integer] the number of prime numbers, starting from 0, you want to find
28
- # @return [Immutable::SortedSet<Integer>] the first `n` prime numbers
29
- def self.first(number_of_primes)
30
- # In Ruby 2.6, you can generate a Range up to infinity with `(2..)` 😍
31
- primes = (2..INFINITY).
32
- lazy.
33
- reject { |i| (2...i).any? { |divisor| (i % divisor).zero? } }.
34
- take(number_of_primes).
35
- to_a
36
-
37
- Immutable::SortedSet[*primes]
38
- end
39
- end
40
- end