collatz 0.0.4 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +12 -2
- data/Gemfile +2 -0
- data/Gemfile.lock +8 -2
- data/Makefile +8 -2
- data/README.md +21 -4
- data/Rakefile +23 -0
- data/collatz.gemspec +1 -1
- data/devlog.md +27 -0
- data/lib/collatz/version.rb +1 -1
- data/lib/collatz.rb +232 -3
- metadata +7 -8
- data/sig/collatz.rbs +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 189ee8518b71ff1013f294eb8da44f83f7d0dcdcb9409c17a50a215db5cb3b6f
|
4
|
+
data.tar.gz: 3b19474f8148f22e8c0191be3ce6ed530ea1aadd2e4954c33774679ff05da733
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 36abd5f5f2a213a38dce9f293bef02d19e551499fb82a92a6b9580312efe5031a9dea5443190877eef6e08e077983f8863482b5ca9e23d0fa9eb30c2b997b988
|
7
|
+
data.tar.gz: 32a82e1b51f5eb0dbe9e10655156d86c8ab729dbfeec10c4b5724cc40b7ca6fcfa43551e67f41db0d5bb326ebb848355c5136e72e82cd10551325468c93d08cf
|
data/.rubocop.yml
CHANGED
@@ -1,10 +1,9 @@
|
|
1
1
|
AllCops:
|
2
2
|
TargetRubyVersion: 2.7.0
|
3
3
|
UseCache: false
|
4
|
+
SuggestExtensions: false
|
4
5
|
# NewCops: enable # would silence the recommendation
|
5
6
|
# # to enable new, but would hide which ones were run
|
6
|
-
# SuggestExtensions: false # would silence the tips
|
7
|
-
# # for recommended suggested gems.
|
8
7
|
|
9
8
|
Gemspec/DeprecatedAttributeAssignment:
|
10
9
|
Enabled: true
|
@@ -13,6 +12,8 @@ Gemspec/RequireMFA:
|
|
13
12
|
|
14
13
|
Naming/BlockForwarding:
|
15
14
|
Enabled: true
|
15
|
+
Naming/MethodParameterName:
|
16
|
+
MinNameLength: 1
|
16
17
|
|
17
18
|
Security/CompoundHash:
|
18
19
|
Enabled: true
|
@@ -27,9 +28,16 @@ Layout/LineContinuationSpacing:
|
|
27
28
|
Enabled: true
|
28
29
|
Layout/LineEndStringConcatenationIndentation:
|
29
30
|
Enabled: true
|
31
|
+
Layout/SpaceAroundOperators:
|
32
|
+
Enabled: false
|
30
33
|
Layout/SpaceBeforeBrackets:
|
31
34
|
Enabled: true
|
32
35
|
|
36
|
+
Metrics/BlockLength:
|
37
|
+
AllowedMethods: ['describe', 'context']
|
38
|
+
Metrics/ParameterLists:
|
39
|
+
Enabled: false
|
40
|
+
|
33
41
|
Lint/AmbiguousAssignment:
|
34
42
|
Enabled: true
|
35
43
|
Lint/AmbiguousOperatorPrecedence:
|
@@ -136,6 +144,8 @@ Style/NumberedParameters:
|
|
136
144
|
Enabled: true
|
137
145
|
Style/NumberedParametersLimit:
|
138
146
|
Enabled: true
|
147
|
+
Style/NumericLiterals:
|
148
|
+
Enabled: false
|
139
149
|
Style/ObjectThen:
|
140
150
|
Enabled: true
|
141
151
|
Style/OpenStructUse:
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
collatz (0.0
|
4
|
+
collatz (0.1.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
@@ -12,8 +12,12 @@ GEM
|
|
12
12
|
parallel (1.22.1)
|
13
13
|
parser (3.1.2.1)
|
14
14
|
ast (~> 2.4.1)
|
15
|
+
psych (4.0.5)
|
16
|
+
stringio
|
15
17
|
rainbow (3.1.1)
|
16
18
|
rake (13.0.6)
|
19
|
+
rdoc (6.4.0)
|
20
|
+
psych (>= 4.0.0)
|
17
21
|
regexp_parser (2.5.0)
|
18
22
|
rexml (3.2.5)
|
19
23
|
rspec (3.11.0)
|
@@ -42,6 +46,7 @@ GEM
|
|
42
46
|
rubocop-ast (1.21.0)
|
43
47
|
parser (>= 3.1.1.0)
|
44
48
|
ruby-progressbar (1.11.0)
|
49
|
+
stringio (3.0.2)
|
45
50
|
unicode-display_width (2.2.0)
|
46
51
|
|
47
52
|
PLATFORMS
|
@@ -50,8 +55,9 @@ PLATFORMS
|
|
50
55
|
DEPENDENCIES
|
51
56
|
collatz!
|
52
57
|
rake (~> 13.0)
|
58
|
+
rdoc (~> 6.0)
|
53
59
|
rspec (~> 3.0)
|
54
60
|
rubocop (~> 1.21)
|
55
61
|
|
56
62
|
BUNDLED WITH
|
57
|
-
2.3.
|
63
|
+
2.3.21
|
data/Makefile
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
.PHONY: setup setup_github clean test build install push_rubygems push_github
|
1
|
+
.PHONY: setup setup_github clean docs test build install push_rubygems push_github
|
2
2
|
SHELL:=/bin/bash
|
3
3
|
|
4
4
|
# Assumes `gem install bundler`
|
@@ -10,11 +10,17 @@ setup_github:
|
|
10
10
|
|
11
11
|
clean:
|
12
12
|
bundle exec rake clean
|
13
|
+
bundle exec rake clobber
|
13
14
|
rm -f collatz-*.gem
|
14
15
|
rm -f pkg/collatz-*.gem
|
15
16
|
|
17
|
+
docs: clean
|
18
|
+
bundle exec rake rdoc
|
19
|
+
|
20
|
+
# default (just `rake`) is spec + rubocop, but be pedantic in case this changes.
|
16
21
|
test: clean
|
17
|
-
bundle exec rake
|
22
|
+
bundle exec rake spec
|
23
|
+
bundle exec rake rubocop
|
18
24
|
|
19
25
|
# We can choose from `gem build collatz.gemspec` or `bundle exec rake build`.
|
20
26
|
# The gem build command creates a ./collatz-$VER.gem file, and the rake build
|
data/README.md
CHANGED
@@ -1,20 +1,37 @@
|
|
1
1
|
# [Collatz](https://github.com/Skenvy/Collatz): [Ruby](https://github.com/Skenvy/Collatz/tree/main/ruby) 🔻💎🔻
|
2
2
|
Functions related to [the Collatz/Syracuse/3N+1 problem](https://en.wikipedia.org/wiki/Collatz_conjecture), implemented in [Ruby](https://www.ruby-lang.org/).
|
3
3
|
## Getting Started
|
4
|
-
[To install the latest from RubyGems](https://rubygems.org/
|
4
|
+
[To install the latest from RubyGems](https://rubygems.org/gems/collatz);
|
5
5
|
```sh
|
6
6
|
gem install collatz
|
7
7
|
```
|
8
|
+
[Or to install from GitHub's hosted gems](https://github.com/Skenvy/Collatz/packages/1636643);
|
9
|
+
```sh
|
10
|
+
gem install collatz --source "https://rubygems.pkg.github.com/skenvy"
|
11
|
+
```
|
12
|
+
### Add to the Gemfile
|
13
|
+
[Add the RubyGems hosted gem](https://rubygems.org/gems/collatz);
|
14
|
+
```ruby
|
15
|
+
gem "collatz", ">= 0.1.0
|
16
|
+
```
|
17
|
+
[Add the GitHub hosted gem](https://github.com/Skenvy/Collatz/packages/1636643);
|
18
|
+
```ruby
|
19
|
+
source "https://rubygems.pkg.github.com/skenvy" do
|
20
|
+
gem "collatz", ">= 0.1.0"
|
21
|
+
end
|
22
|
+
```
|
8
23
|
## Usage
|
9
24
|
Provides the basic functionality to interact with the Collatz conjecture.
|
10
25
|
The parameterisation uses the same `(P,a,b)` notation as Conway's generalisations.
|
11
26
|
Besides the function and reverse function, there is also functionality to retrieve the hailstone sequence, the "stopping time"/"total stopping time", or tree-graph.
|
12
27
|
The only restriction placed on parameters is that both `P` and `a` can't be `0`.
|
13
|
-
## [
|
28
|
+
## [RDoc generated docs](https://skenvy.github.io/Collatz/ruby)
|
14
29
|
## Developing
|
15
30
|
### The first time setup
|
16
31
|
```sh
|
17
|
-
git clone https://github.com/Skenvy/Collatz.git && cd Collatz/ruby &&
|
32
|
+
git clone https://github.com/Skenvy/Collatz.git && cd Collatz/ruby && make setup
|
18
33
|
```
|
19
34
|
### Iterative development
|
20
|
-
|
35
|
+
The majority of `make` recipes for this are just wrapping a `bundle` invocation of `rake`.
|
36
|
+
* `make docs` will recreate the RDoc docs
|
37
|
+
* `make test` will run both the RSpec tests and the RuboCop linter.
|
data/Rakefile
CHANGED
@@ -10,3 +10,26 @@ require "rubocop/rake_task"
|
|
10
10
|
RuboCop::RakeTask.new
|
11
11
|
|
12
12
|
task default: %i[spec rubocop]
|
13
|
+
|
14
|
+
# The below recommended way to add rdoc to rake from rdoc's site has a lot of
|
15
|
+
# things it will compain about, and wont work OotB, so use 'rdoc/task' below.
|
16
|
+
# require 'rdoc/rdoc'
|
17
|
+
# options = RDoc::Options.new
|
18
|
+
# # see RDoc::Options
|
19
|
+
# options.rdoc_include << ["lib/*.rb"]
|
20
|
+
# rdoc = RDoc::RDoc.new
|
21
|
+
# rdoc.document options
|
22
|
+
# # see RDoc::RDoc
|
23
|
+
|
24
|
+
# https://ruby.github.io/rdoc/RDocTask.html
|
25
|
+
# doc is the default output location of the rdoc binary, and is the location
|
26
|
+
# added in the standard ruby gitignore, but the rake task defaults to ./html
|
27
|
+
# and we need to use the rdoc_dir option to change it to doc.
|
28
|
+
|
29
|
+
require "rdoc/task"
|
30
|
+
|
31
|
+
RDoc::Task.new do |rdoc|
|
32
|
+
rdoc.rdoc_dir = "doc"
|
33
|
+
rdoc.main = "README.md" # !? README.rdoc ?!
|
34
|
+
rdoc.rdoc_files.include("README.md", "lib/*.rb")
|
35
|
+
end
|
data/collatz.gemspec
CHANGED
@@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
|
|
14
14
|
Besides the function and reverse function, there is also functionality to retrieve
|
15
15
|
the hailstone sequence, the \"stopping time\"/\"total stopping time\", or tree-graph.
|
16
16
|
The only restriction placed on parameters is that both P and a can't be 0."
|
17
|
-
spec.homepage = "https://github.
|
17
|
+
spec.homepage = "https://skenvy.github.io/Collatz/ruby/"
|
18
18
|
spec.required_ruby_version = ">= 2.7.0"
|
19
19
|
spec.metadata["homepage_uri"] = spec.homepage
|
20
20
|
spec.metadata["source_code_uri"] = "https://github.com/Skenvy/Collatz/tree/main/ruby"
|
data/devlog.md
CHANGED
@@ -53,3 +53,30 @@ While the [GitHub: Working with the RubyGems registry](https://docs.github.com/e
|
|
53
53
|
:github: Bearer <PAT|TOKEN>
|
54
54
|
```
|
55
55
|
We can `base64 ~/.gem/credentials` the rubygems_api_key we got from the `gem signin`, add the string (without the newlines) to our github secrets, and then do something like `echo -n "$RUBYGEMS_API_KEY_BASE64" | base64 --decode > ~/.gem/credentials` to recreate the credentials. GitHub's credentials should be passable with a `echo -e "---\n:github: Bearer <PAT|TOKEN>" > ~/.gem/credentials`.
|
56
|
+
|
57
|
+
With that now merged we can work on adding the functionality and setting up documentation. The two main choices for documentation generation appear to be [rdoc](https://github.com/ruby/rdoc) or [yard](https://yardoc.org/). Yard looks alright, but we're trying to stick to any "official" way of doing something, and with rdoc being put out by ruby, we'll start off with rdoc. We can [`bundle add rdoc --version "~> 6.0" --verbose`](https://bundler.io/man/bundle-add.1.html), but doing this just leads to it permanently hanging on a ""HTTP GET https://index.rubygems.org/versions" immediately after a "HTTP 416 Range Not Satisfiable https://index.rubygems.org/versions". There's quite a few hits for this or similar issues cropping up, but none with a definitive answer or cause, and none that worked to fix the issue here. It took a 20 minute wait for it to finally do anything, then kept asking for my sudo password and refusing to accept it. I retried and it succeeded much quicker, although still took 4 entries of my password to get it to successfully pass and hit that "Bundle complete! 5 Gemfile dependencies, 23 gems now installed". Following the [rdoc](https://github.com/ruby/rdoc) guide to add rdoc to the rakefile leads to a ``rake aborted! NoMethodError: undefined method `delete_if' for nil:NilClass`` which could be connected to [this issue](https://github.com/ruby/rdoc/issues/352), namely ["You haven't told RDoc to document any files"](https://github.com/ruby/rdoc/issues/352#issuecomment-110185993). We can avoid this issue if we use the [rdoc task](https://ruby.github.io/rdoc/RDocTask.html) instead.
|
58
|
+
|
59
|
+
As we start to add the first few lines to the main rb file, we've run into yet another rubocop yelling about something inconsequential (Style/NumericLiterals isn't even listed in our rubocop yaml, so we'll have to add it to disable it);
|
60
|
+
```
|
61
|
+
lib/collatz.rb:19:20: C: [Correctable] Style/NumericLiterals: Use underscores(_) as thousands separator and separate every 3 digits with them.
|
62
|
+
VERIFIED_MAXIMUM = 295147905179352825856
|
63
|
+
^^^^^^^^^^^^^^^^^^^^^
|
64
|
+
```
|
65
|
+
We'll also have to add the cop `Naming/MethodParameterName: MinNameLength: 1` to stop it from complaining about all our uses of `"P"`, `"a"`, and `"b"`. Also worth mentioning is that, unlike other implementations, in ruby we can't use a capital letter to start a non-constant value, so we have to swap all capital `"P"` for lowercase `"p"`'s, lest we get the `"Formal argument cannot be a constant"` error. I'm also cautious of `Style/RedundantReturn` -- I'd like to try and stick to the normal ruby style, but it relies on the novel awareness that the last line of a function without an explicit return is returned. I'll have to simply remember that for later. Another cop is complaining about `Lint/AmbiguousOperatorPrecedence: Wrap expressions with varying precedence with parentheses to avoid ambiguity.`, which seems absurd. After fixing some of these, my line `if n%p == 0 then n/p else ((a*n)+b) end` is still generating the following errors;
|
66
|
+
```
|
67
|
+
lib/collatz.rb:96:3: C: [Correctable] Style/OneLineConditional: Favor the ternary operator (?:) or multi-line constructs over single-line if/then/else/end constructs.
|
68
|
+
if n%p == 0 then n/p else ((a*n)+b) end
|
69
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
70
|
+
lib/collatz.rb:96:6: C: [Correctable] Style/NumericPredicate: Use (n%p).zero? instead of n%p == 0.
|
71
|
+
if n%p == 0 then n/p else ((a*n)+b) end
|
72
|
+
^^^^^^^^
|
73
|
+
```
|
74
|
+
Rubocop is happy if we replace it with `(n%p).zero? ? (n/p) : ((a*n)+b)`, which feels so much less readable, and absurd than anyone would consider that more "readable" than the verbose `if/then/else/end`. Another odd ambiguity is that an error can be raised either with `raise StandardError, 'message'` or `raise StandardError.new('message')` or `raise StandardError.new 'message'`, but the `Style/RaiseArgs` cop has a preference for `raise StandardError, 'message'`. Now we just have to configure the `Metrics/BlockLength` cop which doesn't like all the lines in the spec test. A good suggestion is to set `IgnoredMethods: ['describe', 'context']` according to the docs, which does silence the error, but now returns ``Warning: obsolete parameter `IgnoredMethods` (for `Metrics/BlockLength`) found in .rubocop.yml `IgnoredMethods` has been renamed to `AllowedMethods` and/or `AllowedPatterns`.``. So why are they not mentioned as such on the [rubocop docs](https://docs.rubocop.org/rubocop/cops_metrics.html#metricsblocklength). It feels like a lot of the problems we've run into so far with ruby have been the same as those with go, where it's been historically developed at a pace greater than which it is then adopted, leading to a fragmented ecosystem, although not as badly as it is in go, with solutions to circumvent the at times antithetical expectations easier to find than they were for go. Although a large portion of these issues have been with rubocop. It might be useful, but it adds a lot of noise that needs to be hushed while trying to learn ruby for the first time, even if its suggestions might be temporarily useful. I will say though that setting up the gem deployment was much easier than the maven deployment for java, so all in all it's not too much of a hassle.
|
75
|
+
|
76
|
+
The last thing worth mentioning before trying to release 0.1.0 with the function and reverse function, is that there seems to be a bit of a difference in how to provide "kwargs" than in python. In python kwargs are listed as `some_arg_name = some_default_value`, and can then be referenced by name in the function invocation, or supplied positionally, and without specifying a `**kwargs` to splatter additionl inputs, any name supplied that is not specified as an input will cause an error. In ruby, kwargs are listed as `some_arg_name: some_default_value`, and like python, an error will be thrown if a named kwarg is provided on invocation that isn't specified in the function inputs, however it **can only** be supplied to the invocation per its keyword name, it can't be supplied positionally. Meanwhile, "optional" args in ruby that are specified in the function inputs via `some_arg_name = some_default_value` aren't kwargs, but rather positional args that happen to have default values; they can't be supplied to a function invocation by name, only by position, and a quirk of testing how to assign them to learn the difference between ruby and python demonstrates that while specifying `**kwargs` to splatter inputs in python means parameters can be supplied to the invocation by name that weren't specified in the input, attempting to see how "optional args" affect splattering in ruby leads to a false positive of demonstrating something akin to, but not exactly splattering. That is, if some `some_other_arg_name = some_other_value` is supplied to a function invocation in ruby as the input to an "optional arg", this is not splatting the differently named inputs, rather, it is assigning the value to the name as a variable, and providing that value as the input to the positional optional args. So to achieve true "kwargs" in ruby, we need to use `some_arg_name: some_default_value`, and specify them by name in the invocations.
|
77
|
+
|
78
|
+
We're now ready to add once again create an empty orphan branch;
|
79
|
+
1. `git checkout --orphan gh-pages-ruby`
|
80
|
+
1. `rm .git/index ; git clean -fdx`
|
81
|
+
1. `git commit -m "Initial empty orphan" --allow-empty`
|
82
|
+
1. `git push --set-upstream origin gh-pages-ruby`
|
data/lib/collatz/version.rb
CHANGED
data/lib/collatz.rb
CHANGED
@@ -2,7 +2,236 @@
|
|
2
2
|
|
3
3
|
require_relative "collatz/version"
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
# :section: Main
|
6
|
+
# Provides the basic functionality to interact with the Collatz conjecture. The
|
7
|
+
# parameterisation uses the same (p,a,b) notation as Conway's generalisations.
|
8
|
+
# Besides the function and reverse function, there is also functionality to
|
9
|
+
# retrieve the hailstone sequence, the "stopping time"/"total stopping time", or
|
10
|
+
# tree-graph.
|
11
|
+
|
12
|
+
# The four known cycles for the standard parameterisation.
|
13
|
+
KNOWN_CYCLES = [
|
14
|
+
[1, 4, 2].freeze, [-1, -2].freeze, [-5, -14, -7, -20, -10].freeze,
|
15
|
+
[-17, -50, -25, -74, -37, -110, -55, -164, -82, -41, -122, -61, -182, -91, -272, -136, -68, -34].freeze
|
16
|
+
].freeze
|
17
|
+
# The current value up to which the standard parameterisation has been verified.
|
18
|
+
VERIFIED_MAXIMUM = 295147905179352825856
|
19
|
+
# The current value down to which the standard parameterisation has been verified.
|
20
|
+
VERIFIED_MINIMUM = -272 # TODO: Check the actual lowest bound.
|
21
|
+
|
22
|
+
# Error message constants, to be used as input to the FailedSaneParameterCheck.
|
23
|
+
module SaneParameterErrMsg
|
24
|
+
# Message to print in the FailedSaneParameterCheck if p, the modulus, is zero.
|
25
|
+
SANE_PARAMS_P = "'p' should not be 0 ~ violates modulo being non-zero."
|
26
|
+
# Message to print in the FailedSaneParameterCheck if a, the multiplicand, is zero.
|
27
|
+
SANE_PARAMS_A = "'a' should not be 0 ~ violates the reversability."
|
28
|
+
end
|
29
|
+
|
30
|
+
# Thrown when either p, the modulus, or a, the multiplicand, are zero.
|
31
|
+
class FailedSaneParameterCheck < StandardError
|
32
|
+
# Construct a FailedSaneParameterCheck with a message associated with the provided enum.
|
33
|
+
# @param [String] msg One of the SaneParameterErrMsg strings.
|
34
|
+
def initialize(msg = "This is a custom exception", exception_type = "FailedSaneParameterCheck")
|
35
|
+
@exception_type = exception_type
|
36
|
+
super(msg)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# SequenceState for Cycle Control: Descriptive flags to indicate when some event occurs in the
|
41
|
+
# hailstone sequences or tree graph reversal, when set to verbose, or stopping time check.
|
42
|
+
module SequenceState
|
43
|
+
# A Hailstone sequence state that indicates the stopping
|
44
|
+
# time, a value less than the initial, has been reached.
|
45
|
+
STOPPING_TIME = "STOPPING_TIME"
|
46
|
+
# A Hailstone sequence state that indicates the total
|
47
|
+
# stopping time, a value of 1, has been reached.
|
48
|
+
TOTAL_STOPPING_TIME = "TOTAL_STOPPING_TIME"
|
49
|
+
# A Hailstone and TreeGraph sequence state that indicates the
|
50
|
+
# first occurence of a value that subsequently forms a cycle.
|
51
|
+
CYCLE_INIT = "CYCLE_INIT"
|
52
|
+
# A Hailstone and TreeGraph sequence state that indicates the
|
53
|
+
# last occurence of a value that has already formed a cycle.
|
54
|
+
CYCLE_LENGTH = "CYCLE_LENGTH"
|
55
|
+
# A Hailstone and TreeGraph sequence state that indicates the sequence
|
56
|
+
# or traversal has executed some imposed 'maximum' amount of times.
|
57
|
+
MAX_STOP_OUT_OF_BOUNDS = "MAX_STOP_OUT_OF_BOUNDS"
|
58
|
+
# A Hailstone sequence state that indicates the sequence terminated
|
59
|
+
# by reaching "0", a special type of "stopping time".
|
60
|
+
ZERO_STOP = "ZERO_STOP"
|
61
|
+
end
|
62
|
+
|
63
|
+
# Handles the sanity check for the parameterisation (p,a,b)
|
64
|
+
# required by both the function and reverse function.
|
65
|
+
#
|
66
|
+
# @raise [FailedSaneParameterCheck] If p or a are 0.
|
67
|
+
#
|
68
|
+
# @param [Integer] p Modulus used to devide n, iff n is equivalent to (0 mod p)
|
69
|
+
# @param [Integer] a Factor by which to multiply n.
|
70
|
+
# @param [Integer] b Value to add to the scaled value of n.
|
71
|
+
def assert_sane_parameterisation(p, a, _b)
|
72
|
+
# Sanity check (p,a,b) ~ p absolutely can't be 0. a "could" be zero
|
73
|
+
# theoretically, although would violate the reversability (if ~a is 0 then a
|
74
|
+
# value of "b" as the input to the reverse function would have a pre-emptive
|
75
|
+
# value of every number not divisible by p). The function doesn't _have_ to
|
76
|
+
# be reversable, but we are only interested in dealing with the class of
|
77
|
+
# functions that exhibit behaviour consistant with the collatz function. If
|
78
|
+
# _every_ input not divisable by p went straight to "b", it would simply
|
79
|
+
# cause a cycle consisting of "b" and every b/p^z that is an integer. While
|
80
|
+
# p in [-1, 1] could also be a reasonable check, as it makes every value
|
81
|
+
# either a 1 or 2 length cycle, it's not strictly an illegal operation.
|
82
|
+
# "b" being zero would cause behaviour not consistant with the collatz
|
83
|
+
# function, but would not violate the reversability, so no check either.
|
84
|
+
# " != 0" is redundant for python assertions.
|
85
|
+
raise FailedSaneParameterCheck, SaneParameterErrMsg::SANE_PARAMS_P unless p != 0
|
86
|
+
raise FailedSaneParameterCheck, SaneParameterErrMsg::SANE_PARAMS_A unless a != 0
|
87
|
+
end
|
88
|
+
|
89
|
+
private :assert_sane_parameterisation
|
90
|
+
|
91
|
+
# Returns the output of a single application of a Collatz-esque function.
|
92
|
+
#
|
93
|
+
# @raise [FailedSaneParameterCheck] If p or a are 0.
|
94
|
+
#
|
95
|
+
# @param [Integer] n The value on which to perform the Collatz-esque function.
|
96
|
+
# @param [Integer] p Modulus used to devide n, iff n is equivalent to (0 mod p).
|
97
|
+
# @param [Integer] a Factor by which to multiply n.
|
98
|
+
# @param [Integer] b Value to add to the scaled value of n.
|
99
|
+
#
|
100
|
+
# @return [Integer] The result of the function
|
101
|
+
def function(n, p: 2, a: 3, b: 1)
|
102
|
+
assert_sane_parameterisation(p, a, b)
|
103
|
+
(n%p).zero? ? (n/p) : ((a*n)+b)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Returns the output of a single application of a Collatz-esque reverse function. If
|
107
|
+
# only one value is returned, it is the value that would be divided by p. If two values
|
108
|
+
# are returned, the first is the value that would be divided by p, and the second value
|
109
|
+
# is that which would undergo the multiply and add step, regardless of which is larger.
|
110
|
+
#
|
111
|
+
# @raise [FailedSaneParameterCheck] If p or a are 0.
|
112
|
+
#
|
113
|
+
# @param [Integer] n The value on which to perform the reverse Collatz function.
|
114
|
+
# @param [Integer] p Modulus used to devide n, iff n is equivalent to (0 mod p).
|
115
|
+
# @param [Integer] a Factor by which to multiply n.
|
116
|
+
# @param [Integer] b Value to add to the scaled value of n.
|
117
|
+
#
|
118
|
+
# @return [Integer] The values that would return the input if given to the function.
|
119
|
+
def reverse_function(n, p: 2, a: 3, b: 1)
|
120
|
+
assert_sane_parameterisation(p, a, b)
|
121
|
+
pre_values = [p*n]
|
122
|
+
pre_values += [(n-b)/a] if ((n-b)%a).zero? && !((n-b)%(p*a)).zero?
|
123
|
+
pre_values
|
124
|
+
end
|
125
|
+
|
126
|
+
# Provides the appropriate lambda to use to check if iterations on an initial
|
127
|
+
# value have reached either the stopping time, or total stopping time.
|
128
|
+
# @param [Integer] n The initial value to confirm against a stopping time check.
|
129
|
+
# @param [Boolean] total_stop If false, the lambda will confirm that iterations of n
|
130
|
+
# have reached the oriented stopping time to reach a value closer to 0.
|
131
|
+
# If true, the lambda will simply check equality to 1.
|
132
|
+
# @return [Method(Integer)->(Boolean)] The lambda to check for the stopping time.
|
133
|
+
def stopping_time_terminus(n, total_stop)
|
134
|
+
raise NotImplementedError, "Will be implemented at, or before, v1.0.0"
|
135
|
+
end
|
136
|
+
|
137
|
+
private :stopping_time_terminus
|
138
|
+
|
139
|
+
# Contains the results of computing a hailstone sequence via hailstone_sequence(~).
|
140
|
+
class HailstoneSequence
|
141
|
+
def initialize
|
142
|
+
raise NotImplementedError, "Will be implemented at, or before, v1.0.0"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# Returns a list of successive values obtained by iterating a Collatz-esque
|
147
|
+
# function, until either 1 is reached, or the total amount of iterations
|
148
|
+
# exceeds max_total_stopping_time, unless total_stopping_time is False,
|
149
|
+
# which will terminate the hailstone at the "stopping time" value, i.e. the
|
150
|
+
# first value less than the initial value. While the sequence has the
|
151
|
+
# capability to determine that it has encountered a cycle, the cycle from "1"
|
152
|
+
# wont be attempted or reported as part of a cycle, regardless of default or
|
153
|
+
# custom parameterisation, as "1" is considered a "total stop".
|
154
|
+
#
|
155
|
+
# @raise [FailedSaneParameterCheck] If p or a are 0.
|
156
|
+
#
|
157
|
+
# @param [Integer] initial_value The value to begin the hailstone sequence from.
|
158
|
+
# @param [Integer] p Modulus used to devide n, iff n is equivalent to (0 mod p).
|
159
|
+
# @param [Integer] a Factor by which to multiply n.
|
160
|
+
# @param [Integer] b Value to add to the scaled value of n.
|
161
|
+
# @param [Integer] max_total_stopping_time Maximum amount of times to iterate the function, if 1 is not reached.
|
162
|
+
# @param [Boolean] total_stopping_time Whether or not to execute until the "total" stopping time
|
163
|
+
# (number of iterations to obtain 1) rather than the regular stopping time (number
|
164
|
+
# of iterations to reach a value less than the initial value).
|
165
|
+
#
|
166
|
+
# @return [HailstoneSequence] A set of values that form the hailstone sequence.
|
167
|
+
def hailstone_sequence(initial_value, p: 2, a: 3, b: 1, max_total_stopping_time: 1000, total_stopping_time: true)
|
168
|
+
raise NotImplementedError, "Will be implemented at, or before, v1.0.0"
|
169
|
+
end
|
170
|
+
|
171
|
+
# Returns the stopping time, the amount of iterations required to reach a
|
172
|
+
# value less than the initial value, or nil if max_stopping_time is exceeded.
|
173
|
+
# Alternatively, if total_stopping_time is True, then it will instead count
|
174
|
+
# the amount of iterations to reach 1. If the sequence does not stop, but
|
175
|
+
# instead ends in a cycle, the result will be infinity.
|
176
|
+
# If (p,a,b) are such that it is possible to get stuck on zero, the result
|
177
|
+
# will be the negative of what would otherwise be the "total stopping time"
|
178
|
+
# to reach 1, where 0 is considered a "total stop" that should not occur as
|
179
|
+
# it does form a cycle of length 1.
|
180
|
+
#
|
181
|
+
# @raise [FailedSaneParameterCheck] If p or a are 0.
|
182
|
+
#
|
183
|
+
# @param [Integer] initial_value The value for which to find the stopping time.
|
184
|
+
# @param [Integer] p Modulus used to devide n, iff n is equivalent to (0 mod p).
|
185
|
+
# @param [Integer] a Factor by which to multiply n.
|
186
|
+
# @param [Integer] b Value to add to the scaled value of n.
|
187
|
+
# @param [Integer] max_stopping_time Maximum amount of times to iterate the function, if
|
188
|
+
# the stopping time is not reached. IF the max_stopping_time is reached,
|
189
|
+
# the function will return nil.
|
190
|
+
# @param [Boolean] total_stopping_time Whether or not to execute until the "total" stopping
|
191
|
+
# time (number of iterations to obtain 1) rather than the regular stopping
|
192
|
+
# time (number of iterations to reach a value less than the initial value).
|
193
|
+
#
|
194
|
+
# @return [Integer] The stopping time, or, in a special case, infinity, nil or a negative.
|
195
|
+
def stopping_time(initial_value, p: 2, a: 3, b: 1, max_stopping_time: 1000, total_stopping_time: false)
|
196
|
+
raise NotImplementedError, "Will be implemented at, or before, v1.0.0"
|
197
|
+
end
|
198
|
+
|
199
|
+
# Nodes that form a "tree graph", structured as a tree, with their own node's value,
|
200
|
+
# as well as references to either possible child node, where a node can only ever have
|
201
|
+
# two children, as there are only ever two reverse values. Also records any possible
|
202
|
+
# "terminal sequence state", whether that be that the "orbit distance" has been reached,
|
203
|
+
# as an "out of bounds" stop, which is the regularly expected terminal state. Other
|
204
|
+
# terminal states possible however include the cycle state and cycle length (end) states.
|
205
|
+
class TreeGraphNode
|
206
|
+
def initialize
|
207
|
+
raise NotImplementedError, "Will be implemented at, or before, v1.0.0"
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
# Contains the results of computing the Tree Graph via tree_graph(~).
|
212
|
+
# Contains the root node of a tree of TreeGraphNode's.
|
213
|
+
class TreeGraph
|
214
|
+
def initialize
|
215
|
+
raise NotImplementedError, "Will be implemented at, or before, v1.0.0"
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# Returns a directed tree graph of the reverse function values up to a maximum
|
220
|
+
# nesting of max_orbit_distance, with the initial_value as the root.
|
221
|
+
#
|
222
|
+
# @raise [FailedSaneParameterCheck] If p or a are 0.
|
223
|
+
#
|
224
|
+
# @param [Integer] initial_value The root value of the directed tree graph.
|
225
|
+
# @param [Integer] max_orbit_distance Maximum amount of times to iterate the reverse
|
226
|
+
# function. There is no natural termination to populating the tree graph, equivalent
|
227
|
+
# to the termination of hailstone sequences or stopping time attempts, so this is not
|
228
|
+
# an optional argument like max_stopping_time / max_total_stopping_time, as it is the intended
|
229
|
+
# target of orbits to obtain, rather than a limit to avoid uncapped computation.
|
230
|
+
# @param [Integer] p Modulus used to devide n, iff n is equivalent to (0 mod p).
|
231
|
+
# @param [Integer] a Factor by which to multiply n.
|
232
|
+
# @param [Integer] b Value to add to the scaled value of n.
|
233
|
+
#
|
234
|
+
# @return [TreeGraph] The branches of the tree graph as determined by the reverse function.
|
235
|
+
def tree_graph(initial_value, max_orbit_distance, p: 2, a: 3, b: 1, __cycle_prevention: nil)
|
236
|
+
raise NotImplementedError, "Will be implemented at, or before, v1.0.0"
|
8
237
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: collatz
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nathan Levett
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-09-
|
11
|
+
date: 2022-09-21 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |-
|
14
14
|
Provides the basic functionality to interact with the Collatz conjecture.
|
@@ -35,14 +35,13 @@ files:
|
|
35
35
|
- devlog.md
|
36
36
|
- lib/collatz.rb
|
37
37
|
- lib/collatz/version.rb
|
38
|
-
|
39
|
-
homepage: https://github.com/Skenvy/Collatz/tree/main/ruby
|
38
|
+
homepage: https://skenvy.github.io/Collatz/ruby/
|
40
39
|
licenses:
|
41
40
|
- Apache-2.0
|
42
41
|
metadata:
|
43
|
-
homepage_uri: https://github.
|
42
|
+
homepage_uri: https://skenvy.github.io/Collatz/ruby/
|
44
43
|
source_code_uri: https://github.com/Skenvy/Collatz/tree/main/ruby
|
45
|
-
post_install_message:
|
44
|
+
post_install_message:
|
46
45
|
rdoc_options: []
|
47
46
|
require_paths:
|
48
47
|
- lib
|
@@ -58,7 +57,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
58
57
|
version: '0'
|
59
58
|
requirements: []
|
60
59
|
rubygems_version: 3.1.6
|
61
|
-
signing_key:
|
60
|
+
signing_key:
|
62
61
|
specification_version: 4
|
63
62
|
summary: Functions Related to the Collatz/Syracuse/3n+1 Problem
|
64
63
|
test_files: []
|
data/sig/collatz.rbs
DELETED