fraction-tree 0.1.0

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.
Files changed (3) hide show
  1. checksums.yaml +7 -0
  2. data/lib/fraction_tree.rb +271 -0
  3. metadata +99 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f172fb267db51a11191c850ac073849bd642cd831cafc1c370d6837757fb3138
4
+ data.tar.gz: db36bc04f19e0d7affc3af6c7eba4e9dcdf03f4fe7ac835764e5dfe45655aa39
5
+ SHA512:
6
+ metadata.gz: 27bdbb1bf2f250cc37d111a0df925c70c557c8ea061ce534e7bc94c23631f2be7c04a866d83011771cb6831b82459c7944790c3209d1ffa4c350843eaeb0a063
7
+ data.tar.gz: 3cb3f3b9ca09339d72ce08f5e69a96aebf20de27bffcc107d6fb3321ca5b7fb9dd6f198ac1d6166558a838ff2ee8a0c8e33ff317d97e957507b17b3cf13ee097
@@ -0,0 +1,271 @@
1
+ require "continued_fractions"
2
+
3
+ class FractionTree
4
+ DEFAULT_TREE_DEPTH = 20
5
+
6
+ class << self
7
+ # @return [Array] the boundary nodes of the tree
8
+ # @example
9
+ # FractionTree.base_segment => [(0/1), (1/0)]
10
+ #
11
+ def base_segment
12
+ [Node.new(0,1), Node.new(1,0)]
13
+ end
14
+
15
+ # @return [Array] a multi-dimensional array with the elements of fraction tree, organized by level/row
16
+ # @example
17
+ # FractionTree.tree(4)
18
+ # => [[(0/1), (1/0)],
19
+ # [(1/1)],
20
+ # [(1/2), (2/1)],
21
+ # [(1/3), (2/3), (3/2), (3/1)]]
22
+ #
23
+ # @param number [Integer] the depth of the tree
24
+ #
25
+ def tree(depth=DEFAULT_TREE_DEPTH)
26
+ Array.new(depth, 0).tap do |sbt|
27
+ row = 0
28
+ sbt[row] = base_segment
29
+ i = 2
30
+ while i <= depth do
31
+ figure_from = sbt[0..row].flatten.sort
32
+ new_frow = Array.new(2**(i-2), 0)
33
+ idx = 0
34
+ figure_from.each_cons(2) do |left,right|
35
+ new_frow[idx] = Node.new(left.numerator+right.numerator, left.denominator+right.denominator)
36
+ idx += 1
37
+ end
38
+ row += 1
39
+ sbt[row] = new_frow
40
+ i += 1
41
+ end
42
+ end
43
+ end
44
+
45
+ # @return [Array] a sequence of fraction tree nodes
46
+ # @example
47
+ # FractionTree.sequence(3)
48
+ # => [(0/1), (1/3), (1/2), (2/3), (1/1), (3/2), (2/1), (3/1), (1/0)]
49
+ #
50
+ # @param depth the number of iterations of the algorithm to run. The number of nodes returned will be greater
51
+ # @param segment a tuple array defining the segment of the tree to collect nodes. The elements of the tuple must be instances of FractionTree::Node
52
+ #
53
+ def sequence(depth=5, segment: base_segment)
54
+ [segment.first]+_sequence(depth, segment:)+[segment.last]
55
+ end
56
+
57
+ # @return [Array] set of fraction nodes leading to the given number
58
+ # @example
59
+ # FractionTree.path_to(7/4r) => [(1/1), (2/1), (3/2), (5/3), (7/4)]
60
+ #
61
+ # @param number the target the fraction path leads to
62
+ #
63
+ def path_to(number, find_parents: false, segment: base_segment)
64
+ return Node.new(number.numerator, number.denominator) if number.zero?
65
+ q = Node.new(number.numerator, number.denominator)
66
+ l = segment.first
67
+ h = segment.last
68
+ not_found = true
69
+ parents = []
70
+ results = []
71
+ while not_found
72
+ m = (l + h)
73
+ if m < q
74
+ l = m
75
+ elsif m > q
76
+ h = m
77
+ else
78
+ parents << l << h
79
+ not_found = false
80
+ end
81
+ results << m
82
+ end
83
+ find_parents == false ? results : parents
84
+ end
85
+
86
+ # @return [Array] a pair of fraction tree nodes leading to the given number.
87
+ # For irrational numbers, the parent nodes are one of an infinite series, whose nearness is determined by the limits of the system
88
+ # @example
89
+ # FractionTree.parents_of(15/13r) => [(8/7), (7/6)]
90
+ # FractionTree.parents_of(Math::PI) => [(447288330638589/142376297616907), (436991388364966/139098679093749)]
91
+ #
92
+ # @param num the child number whose parents are being sought
93
+ #
94
+ def parents_of(number)
95
+ path_to(number, find_parents: true)
96
+ end
97
+
98
+ # @return [Array] the ancestors shared by the given descendants
99
+ # @example
100
+ # FractionTree.common_ancestors_between(4/3r, 7/4r)
101
+ # => [(1/1), (2/1), (3/2)]
102
+ #
103
+ # @param number1 one of two descendants
104
+ # @param number2 two of two descendants
105
+ #
106
+ def common_ancestors_between(number1, number2)
107
+ path_to(number1) & path_to(number2)
108
+ end
109
+
110
+ # @return [Array] the descendants of number starting at its parents
111
+ # @example
112
+ # FractionTree.descendancy_from(5/4r, 3)
113
+ # => [(1/1), (7/6), (6/5), (11/9), (5/4), (14/11), (9/7), (13/10), (4/3)]
114
+ #
115
+ # @param number around which descendancy is focused
116
+ # @param depth how many nodes to collect
117
+ #
118
+ def descendancy_from(number, depth=5)
119
+ parent1, parent2 = parents_of(number)
120
+ descendants_of(parent1, parent2, depth)
121
+ end
122
+
123
+ # @return [FractionTree::Node] the mediant child of the given numbers
124
+ # Return nil if bc - ad |= 1, for a/b, c/d
125
+ # @example
126
+ # FractionTree.child_of(1/1r, 4/3r) => (5/4)
127
+ # FractionTree.child_of(7/4r, 4/3r) => nil
128
+ #
129
+ # @param number1 one of two parents
130
+ # @param number2 two of two parents
131
+ #
132
+ def child_of(number1, number2, strict_neighbors: true)
133
+ return nil unless farey_neighbors?(number1, number2) || !strict_neighbors
134
+ Node.new(number1.numerator, number1.denominator) + Node.new(number2.numerator, number2.denominator)
135
+ end
136
+
137
+ # @return [Array] of nodes descended from parent1 and parent2
138
+ # Return empty array if bc - ad |= 1, for a/b, c/d
139
+ # @example
140
+ # FractionTree.descendants_of(1/1r, 4/3r, 3)
141
+ # => [(1/1), (7/6), (6/5), (11/9), (5/4), (14/11), (9/7), (13/10), (4/3)]
142
+ #
143
+ # @param parent1 one of two parents
144
+ # @param parent2 two of two parents
145
+ #
146
+ def descendants_of(parent1, parent2, depth=5, strict_neighbors: true)
147
+ return [] unless farey_neighbors?(parent1, parent2) || !strict_neighbors
148
+ segment = [Node.new(parent1.numerator, parent1.denominator), Node.new(parent2.numerator, parent2.denominator)]
149
+ sequence(depth, segment: segment)
150
+ end
151
+
152
+ # @return [Array] of FractionTree::Nodes leading quotient-wise to number
153
+ # @example
154
+ # FractionTree.quotient_walk(15/13r)
155
+ # => [(1/1), (2/1), (3/2), (4/3), (5/4), (6/5), (7/6), (8/7), (15/13)]
156
+ #
157
+ # @param number to walk toward
158
+ # @param limit the depth of the walk. Useful for irrational numbers
159
+ #
160
+ def quotient_walk(number, limit: 10, segment: computed_base_segment(number))
161
+ iterating_quotients = ContinuedFraction.new(number, limit).quotients.drop(1)
162
+ comparing_node = Node.new(number.numerator, number.denominator)
163
+
164
+ segment.tap do |arr|
165
+ held_node = arr[-2]
166
+ moving_node = arr[-1]
167
+
168
+ iterating_quotients.each do |q|
169
+ (1..q).each do |i|
170
+ arr << held_node + moving_node
171
+ # We don't want to walk past the number when it's a rational number and we've reached it
172
+ break if arr.include?(comparing_node)
173
+ moving_node = arr[-1]
174
+ end
175
+ held_node = arr[-2]
176
+ end
177
+ end
178
+ end
179
+
180
+ # @return [Array] of numerators of the fraction tree nodes. Also known as the Stern-Brocot sequence.
181
+ # @example
182
+ # FractionTree.numeric_sequence.take(12)
183
+ # => [1, 1, 2, 1, 3, 2, 3, 1, 4, 3, 5, 2]
184
+ #
185
+ def numeric_sequence
186
+ return enum_for :numeric_sequence unless block_given?
187
+ a=[1,1]
188
+
189
+ 0.step do |i|
190
+ yield a[i]
191
+ a << a[i]+a[i+1] << a[i+1]
192
+ end
193
+ end
194
+
195
+ private
196
+ def computed_base_segment(number)
197
+ floor = number.floor
198
+ [Node.new(floor,1), Node.new(floor+1,1)]
199
+ end
200
+
201
+ def _sequence(depth = 5, segment:)
202
+ return [] if depth == 0
203
+
204
+ mediant = segment.first + segment.last
205
+
206
+ # Generate left segment, mediant, then right segment
207
+ _sequence(depth - 1, segment: [segment.first, mediant]) + [mediant] + _sequence(depth - 1, segment: [mediant, segment.last])
208
+ end
209
+
210
+ def farey_neighbors?(num1, num2)
211
+ (num1.numerator * num2.denominator - num1.denominator * num2.numerator).abs == 1
212
+ end
213
+ end
214
+
215
+ class Node
216
+ include Comparable
217
+
218
+ attr_accessor :numerator, :denominator, :weight
219
+
220
+ def initialize(n,d)
221
+ @numerator = n
222
+ @denominator = d
223
+ @weight = (d == 0 ? Float::INFINITY : Rational(@numerator, @denominator))
224
+ end
225
+
226
+ alias :to_r :weight
227
+
228
+ def inspect
229
+ "(#{numerator}/#{denominator})"
230
+ end
231
+ alias :to_s :inspect
232
+
233
+ def <=>(rhs)
234
+ self.weight <=> rhs.weight
235
+ end
236
+
237
+ def ==(rhs)
238
+ self.weight == rhs.weight
239
+ end
240
+
241
+ # Needed for intersection operations to work.
242
+ # https://blog.mnishiguchi.com/ruby-intersection-of-object-arrays
243
+ # https://shortrecipes.blogspot.com/2006/10/ruby-intersection-of-two-arrays-of.html
244
+ def eql?(rhs)
245
+ rhs.kind_of?(self.class) && weight == rhs.weight
246
+ end
247
+
248
+ def +(rhs)
249
+ self.class.new(self.numerator+rhs.numerator, self.denominator+rhs.denominator)
250
+ end
251
+ end
252
+ end
253
+
254
+ class SternBrocotTree < FractionTree; end
255
+ class ScaleTree < FractionTree; end
256
+
257
+ class OctaveReducedTree < FractionTree
258
+ def self.base_segment
259
+ [Node.new(1,1), Node.new(2,1)]
260
+ end
261
+ end
262
+
263
+ class FareyTree < FractionTree
264
+ def self.base_segment
265
+ [Node.new(0,1), Node.new(1,1)]
266
+ end
267
+ end
268
+
269
+ class KeyboardTree < FareyTree; end
270
+ class ScaleStepTree < FareyTree; end
271
+ class Log2Tree < FareyTree; end
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fraction-tree
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jose Hales-Garcia
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-09-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: continued_fractions
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.8'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.8'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '3.2'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '3.2'
41
+ - !ruby/object:Gem::Dependency
42
+ name: byebug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '11.1'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '11.1'
55
+ - !ruby/object:Gem::Dependency
56
+ name: yard
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.9'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.9'
69
+ description: A collection of Stern-Brocot based models and methods
70
+ email: jose@halesgarcia.com
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files: []
74
+ files:
75
+ - lib/fraction_tree.rb
76
+ homepage: https://jolohaga.github.io/fraction-tree/
77
+ licenses:
78
+ - MIT
79
+ metadata: {}
80
+ post_install_message:
81
+ rdoc_options: []
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '0'
94
+ requirements: []
95
+ rubygems_version: 3.4.19
96
+ signing_key:
97
+ specification_version: 4
98
+ summary: Fraction tree
99
+ test_files: []