unary_factorial 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c511fb978628b0b06d10374f54d920c5883fd8e5d8d7d32e9f569af38c0b8b66
4
+ data.tar.gz: e865e43dfc2535d9d8298cb02aee1edfea323580f0e3041bf54af801bbfc7d22
5
+ SHA512:
6
+ metadata.gz: 004453a45bee3aa899cde947fbfd1fb6ee82d8aa53b6dc9803399d31978cdb64805b488c9036781880400d77388bbb48c89907269daa86e687aeca67d04c3d06
7
+ data.tar.gz: ac5eb373a42bc84f3ed00a02cab8f6b479e575e94427494a769d865dbbfc53753d81091a3d21276f439576fde30c7515b9398988fd5b0d8fc269d6b7aaa8447e
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../lib/unary_factorial'
4
+ require 'benchmark/ips'
5
+
6
+ using UnaryFactorial.new
7
+
8
+ def bench(target)
9
+ puts "Factorial of #{target}:"
10
+ Benchmark.ips do
11
+ it.report("Normal uncached #{target}") { (2..target).reduce(1, :*) }
12
+ it.report("Unary cached #{target}") { !target }
13
+
14
+ it.compare!
15
+ end
16
+ end
17
+
18
+ bench(2_600)
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../lib/unary_factorial'
4
+ require 'benchmark/ips'
5
+
6
+ using UnaryFactorial.new(cache: nil)
7
+
8
+ def bench(target)
9
+ puts "Factorial of #{target}:"
10
+ Benchmark.ips do
11
+ it.report("Normal uncached #{target}") { (2..target).reduce(1, :*) }
12
+ it.report("Unary uncached #{target}") { !target }
13
+
14
+ it.compare!
15
+ end
16
+ end
17
+
18
+ bench(120)
19
+ bench(2_600)
data/gems.rb ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
6
+
7
+ gem 'minitest', '~> 5'
8
+ gem 'rake', '~> 10'
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # This refinement uses the Schönhage–Strassen algorithm for efficient
5
+ # computation of factorials with results cached by default.
6
+ #
7
+ # Caching dramatically improves performance when portions of the
8
+ # factorial calculation are repeated.
9
+ class UnaryFactorial < Module
10
+ class NegativeError < StandardError
11
+ def initialize(message = 'Factorials cannot be negative') = super
12
+ end
13
+
14
+ attr_accessor :cache
15
+
16
+ ##
17
+ # If the `cache` keyword argument doesn't provide a custom cache,
18
+ # a `Hash` will be used as the default cache. Set `cache` to `nil`
19
+ # to disable caching.
20
+ #
21
+ # A custom `cache` must respond to `[]` and `[]=` and should also
22
+ # respond to `size`, `clear` and `delete` as an accessor interface.
23
+ def initialize(cache: {})
24
+ @cache = cache
25
+
26
+ module_eval do
27
+ refine Integer do
28
+ def !@
29
+ raise NegativeError if negative?
30
+
31
+ factorial(1..self)
32
+ end
33
+
34
+ private
35
+
36
+ define_method :factorial do |range|
37
+ if cache
38
+ value = cache[range]
39
+ return value if value
40
+ end
41
+
42
+ from = range.begin
43
+ to = range.end
44
+ diff = to - from
45
+
46
+ value = case diff
47
+ when 0
48
+ from
49
+ when ..3
50
+ range.reduce(1, :*)
51
+ else
52
+ mid = (to + from) / 2
53
+ factorial(from..mid) * factorial(mid.succ..to)
54
+ end
55
+
56
+ cache[range] = value if cache
57
+
58
+ value
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
data/license.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) Shannon Skipper
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/rakefile.rb ADDED
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ task default: %w[test]
4
+
5
+ task :test do
6
+ Rake::FileList['test/*_test.rb'].each do |file|
7
+ ruby file
8
+ end
9
+ end
10
+
11
+ task :bench do
12
+ Rake::FileList['bench/*_bench.rb'].each do |file|
13
+ ruby file
14
+ end
15
+ end
data/readme.md ADDED
@@ -0,0 +1,56 @@
1
+ # UnaryFactorial
2
+
3
+ This gem refines `Integer` to provide a unary-style interface.
4
+
5
+ ```ruby
6
+ !42
7
+ #=> 1405006117752879898543142606244511569936384000000000
8
+ ```
9
+
10
+ The Schönhage–Strassen algorithm is used for efficient factorial calculation.
11
+
12
+ Optional caching is enabled by default for much faster repeated calculations.
13
+
14
+ ## Usage
15
+
16
+ ```ruby
17
+ require 'unary_factorial'
18
+
19
+ Factorial = UnaryFactorial.new
20
+ using Factorial
21
+
22
+ !5
23
+ #=> 120
24
+
25
+ Factorial.cache
26
+ #=> {1..3 => 6, 4..5 => 20, 1..5 => 120}
27
+ ```
28
+
29
+ ## Optionally disable caching
30
+
31
+ To disable caching, provide a falsey `cache` keyword argument:
32
+
33
+ ```ruby
34
+ require 'unary_factorial'
35
+
36
+ Factorial = UnaryFactorial.new(cache: nil)
37
+
38
+ !5
39
+ #=> 120
40
+
41
+ Factorial.cache
42
+ #=> nil
43
+ ```
44
+
45
+ ## Installation
46
+
47
+ Install the gem directly:
48
+ ```sh
49
+ gem install unary_factorial
50
+ ```
51
+
52
+ Or add the gem to your app's bundle:
53
+ ```
54
+ bundle add unary_factorial
55
+ bundle
56
+ ```
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../lib/unary_factorial'
4
+ require 'minitest/autorun'
5
+ require 'minitest/pride'
6
+
7
+ class UnaryFactorialTest < Minitest::Test
8
+ Factorial = UnaryFactorial.new
9
+ using Factorial
10
+
11
+ def test_cache_basics
12
+ assert_respond_to Factorial, :cache
13
+ assert_kind_of Hash, Factorial.cache
14
+ end
15
+
16
+ def test_cache_contents
17
+ !120
18
+
19
+ assert_equal Factorial.cache.size, 63
20
+ assert_equal Factorial.cache.fetch(69..72), 24_690_960
21
+ end
22
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../lib/unary_factorial'
4
+ require 'minitest/autorun'
5
+ require 'minitest/pride'
6
+
7
+ class UnaryFactorialTest < Minitest::Test
8
+ using UnaryFactorial.new
9
+
10
+ def test_small_factorials
11
+ assert_equal 1, !0
12
+ assert_equal 1, !1
13
+ assert_equal 2, !2
14
+ assert_equal 6, !3
15
+ assert_equal 24, !4
16
+ assert_equal 120, !5
17
+ assert_equal 3_628_800, !10
18
+ assert_equal 2_432_902_008_176_640_000, !20
19
+ end
20
+
21
+ def test_larger_factorials
22
+ assert_equal 30_414_093_201_713_378_043_612_608_166_064_768_844_377_641_568_960_512_000_000_000_000, !50
23
+ assert_equal 8_320_987_112_741_390_144_276_341_183_223_364_380_754_172_606_361_245_952_449_277_696_409_600_000_000_000_000,
24
+ !60
25
+ end
26
+
27
+ def test_negatives_error
28
+ assert_raises(UnaryFactorial::NegativeError) { !-1 }
29
+ assert_raises(UnaryFactorial::NegativeError) { !-42 }
30
+ end
31
+ end
metadata ADDED
@@ -0,0 +1,52 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: unary_factorial
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Shannon Skipper
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2025-03-04 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: An Integer refinement gem for efficient Schönhage–Strassen factorial
13
+ calculation with optional caching with a unary method interface.
14
+ email:
15
+ - shannonskipper@gmail.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - bench/cached_bench.rb
21
+ - bench/uncached_bench.rb
22
+ - gems.rb
23
+ - lib/unary_factorial.rb
24
+ - license.txt
25
+ - rakefile.rb
26
+ - readme.md
27
+ - test/cache_test.rb
28
+ - test/unary_factorial_test.rb
29
+ homepage: https://github.com/havenwood/unary_factorial
30
+ licenses:
31
+ - MIT
32
+ metadata:
33
+ rubygems_mfa_required: 'true'
34
+ rdoc_options: []
35
+ require_paths:
36
+ - lib
37
+ required_ruby_version: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '3.4'
42
+ required_rubygems_version: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ requirements: []
48
+ rubygems_version: 3.7.0.dev
49
+ specification_version: 4
50
+ summary: A refinement for efficient Schönhage–Strassen factorial calculation with
51
+ caching.
52
+ test_files: []