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 +7 -0
- data/bench/cached_bench.rb +18 -0
- data/bench/uncached_bench.rb +19 -0
- data/gems.rb +8 -0
- data/lib/unary_factorial.rb +63 -0
- data/license.txt +22 -0
- data/rakefile.rb +15 -0
- data/readme.md +56 -0
- data/test/cache_test.rb +22 -0
- data/test/unary_factorial_test.rb +31 -0
- metadata +52 -0
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,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
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
|
+
```
|
data/test/cache_test.rb
ADDED
@@ -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: []
|