fraction-tree 1.1.0 → 2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9d4c2e3c84acc2fa31151b2863bf7c602490e891004198884e8002a7b7f3cc32
4
- data.tar.gz: c335d6bfb3f54c8471835cbc9283d904f8d71f7786cfde02a41448a1840ecb02
3
+ metadata.gz: 6a5099eb066c0e5fae9c4553a44b75ca90b52db8c4711b7d7a07247d116e93ea
4
+ data.tar.gz: 20624486f4b7987331ce3abc1f724a183212d3d403ab207d71a7bdbcf081c3a7
5
5
  SHA512:
6
- metadata.gz: ef4347b24418118dbf6f4c8fdc6ac03829829aaf5c15d51295befda2f6bddad1b7022b7b428906998ab2a18106a68850dacdc61c3616a345d911f5db32008c86
7
- data.tar.gz: 940a05ae9a163d32cae321457da5135334f8a45a96bd0a6141d420d133216c912cd50826c33c3488fa7c17c96286ec11e755e2c8199afe4cddc8fee9846e25bf
6
+ metadata.gz: a9abac107904f305c8a23fd34a4fb838b35ddca05046c7713a1c823c82848b85b914c055b2c1a24b3e2b440cfc04d23ad95fd312df4351d7244cfad0c5850a61
7
+ data.tar.gz: bfac6032e8f9b958e3f766bb16e7160a5ddb56fe2234e6fc79df6fe4317eb823cdcbb374508d1fbd3172b27334a7921a1c0f0c33b82e07ec767c8fc98c116952
@@ -0,0 +1,29 @@
1
+ class String
2
+ # @return [FractionTree::Node] string Stern-Brocot decoded
3
+ # @example
4
+ # "1/0".to_node => (1/0)
5
+ #
6
+ def to_node
7
+ if self.include?(".")
8
+ number = self.to_d
9
+ numerator, denominator = number.numerator, number.denominator
10
+ elsif self.include?("/")
11
+ (numerator, denominator) = self.split("/").map(&:to_i)
12
+ else
13
+ number = self.to_r
14
+ numerator, denominator = number.numerator, number.denominator
15
+ end
16
+ number = denominator.zero? ? Float::INFINITY : Rational(numerator, denominator)
17
+ FractionTree::Node.new(number)
18
+ end
19
+ end
20
+
21
+ class Numeric
22
+ # @return [FractionTree::Node] string Stern-Brocot decoded
23
+ # @example
24
+ # Float::INFINITY.to_node => (1/0)
25
+ #
26
+ def to_node
27
+ FractionTree.node(self)
28
+ end
29
+ end
@@ -0,0 +1,256 @@
1
+ # @author Jose Hales-Garcia
2
+ #
3
+ class FractionTree
4
+ DEFAULT_TREE_DEPTH = 20
5
+
6
+ private_class_method :new
7
+
8
+ class << self
9
+ # @return the left-most node of the range of the tree
10
+ # @example
11
+ # FractionTree.left_node => 0/1
12
+ # @note defaults to Stern-Brocot left-most range, 0
13
+ #
14
+ def left_node
15
+ @left_node || 0/1r
16
+ end
17
+
18
+ # @return the right-most node of the range of the tree
19
+ # @example
20
+ # FractionTree.right_node => Infinity
21
+ # @note defaults to Stern-Brocot right-most range, Infinity
22
+ #
23
+ def right_node
24
+ @right_node || Float::INFINITY
25
+ end
26
+
27
+ # Set the left-most node of the range of the tree
28
+ # @example
29
+ # FractionTree.left_node = 1/1r
30
+ #
31
+ def left_node=(rhs)
32
+ @left_node = rhs
33
+ end
34
+
35
+ # Set the right-most node of the range of the tree
36
+ # @example
37
+ # FractionTree.right_node = 2/1r
38
+ #
39
+ def right_node=(rhs)
40
+ @right_node = rhs
41
+ end
42
+
43
+ # @return the range of the tree
44
+ # @example
45
+ # FractionTree.range => (0/1..Infinity)
46
+ # @note defaults to Stern-Brocot range, (0..Infinity)
47
+ #
48
+ def range
49
+ (left_node..right_node)
50
+ end
51
+
52
+ # Set the range of the tree
53
+ # @example
54
+ # FractionTree.range = :farey
55
+ # => (0/1..1/1)
56
+ # @note Accepts keywords:
57
+ # :farey, :keyboard, :scale_step, :log2 => (0/1..1/1)
58
+ # :stern_brocot, :scale => (0/1..1/0)
59
+ # :octave_reduced => (1/1..2/1)
60
+ #
61
+ def range=(rhs)
62
+ case rhs
63
+ when :farey, :keyboard, :scale_step, :log2
64
+ @left_node, @right_node = 0/1r, 1/1r
65
+ when :stern_brocot, :scale
66
+ @left_node, @right_node = 0/1r, Float::INFINITY
67
+ when :octave_reduced
68
+ @left_node, @right_node = 1/1r, 2/1r
69
+ else
70
+ @left_node = @right_node = nil
71
+ end
72
+ end
73
+
74
+ # The cache of nodes used for faster lookup
75
+ # @note Intended for internal use.
76
+ def nodes
77
+ @@nodes ||= {}
78
+ end
79
+
80
+ # Reset the cache of nodes
81
+ # @example
82
+ # FractionTree.reset_nodes => {}
83
+ #
84
+ def reset_nodes
85
+ @@nodes = {}
86
+ end
87
+
88
+ # @return [FractionTree::Node] the node in the tree representing the given number
89
+ # @example
90
+ # FractionTree.node(3/2r) => (3/2)
91
+ #
92
+ def node(number)
93
+ validate(number)
94
+ nodes[number] ||= Node.new(number)
95
+ end
96
+
97
+ # @return [FractionTree::Node] the node decoded from the given string
98
+ # @example
99
+ # FractionTree.decode("RLL") => (4/3)
100
+ #
101
+ def decode(str)
102
+ wrk_node = Node.decode(str)
103
+ nodes[wrk_node.number] ||= wrk_node
104
+ end
105
+
106
+ # @return [FractionTree::Node] the mediant sum of the given numbers
107
+ # @example
108
+ # FractionTree.mediant_sum(3/2r, 4/3r) => (7/5)
109
+ #
110
+ def mediant_sum(n1, n2)
111
+ Node.new(n1) + Node.new(n2)
112
+ end
113
+
114
+ # @return [Boolean] whether two numbers are neighbors
115
+ # @example
116
+ # FractionTree.neighbors?(3/2r, 4/3r) => true
117
+ # FractionTree.neighbors?(3/2r, 7/4r) => false
118
+ # FractionTree.neighbors?(2/1r, Float::INFINITY) => true
119
+ # @param number1 of comparison
120
+ # @param number2 of comparison
121
+ # @note Neighbor definition: abs(a * d - b * c) = 1, for a/b, c/d
122
+ # @note Float::INFINITY => 1/0
123
+ #
124
+ def neighbors?(number1, number2)
125
+ (a, b) = number1.infinite? ? [1, 0] : [number1.numerator, number1.denominator]
126
+ (c, d) = number2.infinite? ? [1, 0] : [number2.numerator, number2.denominator]
127
+ (a * d - b * c).abs == 1
128
+ end
129
+
130
+ # @return [Array] a multi-dimensional array of fraction tree nodes
131
+ # @example
132
+ # FractionTree.tree(depth: 4)
133
+ # => [[(0/1), (1/0)],
134
+ # [(1/1)],
135
+ # [(1/2), (2/1)],
136
+ # [(1/3), (2/3), (3/2), (3/1)]]
137
+ #
138
+ # @param depth [Integer] the depth of the tree
139
+ # @param left_node [FractionTree::Node] the left starting node
140
+ # @param right_node [FractionTree::Node] the right starting node
141
+ #
142
+ def tree(depth: 10, left_node: default_left_node, right_node: default_right_node)
143
+ Array.new(depth, 0).tap do |sbt|
144
+ row = 0
145
+ sbt[row] = [left_node, right_node]
146
+ i = 2
147
+ while i <= depth do
148
+ figure_from = sbt[0..row].flatten.sort
149
+ new_frow = Array.new(2**(i-2), 0)
150
+ idx = 0
151
+ figure_from.each_cons(2) do |left, right|
152
+ new_frow[idx] = left + right
153
+ idx += 1
154
+ end
155
+ row += 1
156
+ sbt[row] = new_frow
157
+ i += 1
158
+ end
159
+ end
160
+ end
161
+
162
+ # @return [FractionTree::Node] the mediant child of the given numbers
163
+ # @example
164
+ # FractionTree.child_of(1/1r, 4/3r) => (5/4)
165
+ # FractionTree.child_of(7/4r, 4/3r) => nil
166
+ #
167
+ # @param number1 [Rational] one of two parents
168
+ # @param number2 [Rational] two of two parents
169
+ # @note return nil if bc - ad |= 1, for a/b, c/d
170
+ #
171
+ def child_of(number1, number2)
172
+ return nil unless neighbors?(number1, number2)
173
+ # node(number1.numerator, number1.denominator) + node(number2.numerator, number2.denominator)
174
+ node(number1) + node(number2)
175
+ end
176
+
177
+ # @return [Array] of fraction tree nodes descended from parent1 and parent2
178
+ # Return empty array if bc - ad |= 1, for a/b, c/d
179
+ # @example
180
+ # FractionTree.descendants_of(1/1r, 4/3r)
181
+ # => [(1/1), (7/6), (6/5), (11/9), (5/4), (14/11), (9/7), (13/10), (4/3)]
182
+ #
183
+ # @param parent1 [Rational] one of two parents
184
+ # @param parent2 [Rational] two of two parents
185
+ # @param depth [Integer] the depth to collect
186
+ #
187
+ def descendants_of(parent1, parent2, depth: 5)
188
+ return [] unless neighbors?(parent1, parent2)
189
+ sequence(depth:, left_node: Node.new(parent1), right_node: Node.new(parent2))
190
+ end
191
+
192
+ # @return [Array] a sequence of fraction tree nodes
193
+ # @example
194
+ # FractionTree.new.sequence(3)
195
+ # => [(0/1), (1/3), (1/2), (2/3), (1/1), (3/2), (2/1), (3/1), (1/0)]
196
+ #
197
+ # @param depth [Integer] the number of iterations of the algorithm to run. The number of nodes returned will be greater
198
+ # @param left_node [FractionTree::Node] the left starting node
199
+ # @param right_node [FractionTree::Node] the right starting node
200
+ #
201
+ def sequence(depth: 5, left_node: default_left_node, right_node: default_right_node)
202
+ [left_node]+_sequence(depth:, left_node:, right_node:)+[right_node]
203
+ end
204
+
205
+ # @return [Array] of numerators of the fraction tree nodes. Aka the Stern-Brocot sequence.
206
+ # @example
207
+ # FractionTree.numeric_sequence.take(12)
208
+ # => [1, 1, 2, 1, 3, 2, 3, 1, 4, 3, 5, 2]
209
+ #
210
+ def numeric_sequence
211
+ return enum_for :numeric_sequence unless block_given?
212
+ a=[1,1]
213
+
214
+ 0.step do |i|
215
+ yield a[i]
216
+ a << a[i]+a[i+1] << a[i+1]
217
+ end
218
+ end
219
+
220
+ private
221
+ def validate(num)
222
+ raise(ArgumentError, "#{num} not in range of #{range}", caller[0]) unless range.include?(num)
223
+ end
224
+
225
+ def default_left_node
226
+ node(left_node)
227
+ end
228
+
229
+ def default_right_node
230
+ node(right_node)
231
+ end
232
+
233
+ def _node(num, den=nil)
234
+ if num.kind_of?(Float)
235
+ num = num.to_d
236
+ end
237
+ if num.infinite?
238
+ num, den = 1, 0
239
+ end
240
+ if den.nil?
241
+ den = num.denominator
242
+ num = num.numerator
243
+ end
244
+ Node.new(num, den)
245
+ end
246
+
247
+ def _sequence(depth: 5, left_node:, right_node:)
248
+ return [] if depth == 0
249
+
250
+ mediant = left_node + right_node
251
+
252
+ # Generate left segment, mediant, then right segment
253
+ _sequence(depth: depth - 1, left_node:, right_node: mediant) + [mediant] + _sequence(depth: depth - 1, left_node: mediant, right_node:)
254
+ end
255
+ end
256
+ end
@@ -0,0 +1,252 @@
1
+ # @author Jose Hales-Garcia
2
+ #
3
+ class FractionTree
4
+ class Node
5
+ extend Forwardable
6
+ include Comparable
7
+
8
+ def_delegators :@number, :zero?, :infinite?
9
+
10
+ attr_reader :numerator, :denominator, :number
11
+
12
+ IDENTITY_MATRIX = Matrix.identity(2)
13
+ LEFT_MATRIX = Matrix[[1,1],[0,1]]
14
+ RIGHT_MATRIX = Matrix[[1,0],[1,1]]
15
+
16
+ def initialize(num)
17
+ (@numerator, @denominator) = fraction_pair(num)
18
+ @number = num
19
+ end
20
+
21
+ alias :to_r :number
22
+
23
+ class << self
24
+ # @return [FractionTree::Node] the fraction decoded from the given string
25
+ # @example
26
+ # FractionTree::Node.decode("RLL") => (4/3)
27
+ #
28
+ def decode(string)
29
+ result = IDENTITY_MATRIX
30
+
31
+ string.split("").each do |direction|
32
+ case direction
33
+ when "L", "0", "l"
34
+ result = result * LEFT_MATRIX
35
+ when "R", "1", "r"
36
+ result = result * RIGHT_MATRIX
37
+ end
38
+ end
39
+ FractionTree.node(Rational(result.row(1).sum, result.row(0).sum))
40
+ end
41
+
42
+ # @return [String] the Stern-Brocot encoding of number
43
+ # @example
44
+ # FractionTree::Node.encode(4/3r) => "RLL"
45
+ # @param limit of codes to generate
46
+ #
47
+ def encode(number, limit: Float::INFINITY)
48
+ return nil if (number.infinite? || number.zero?)
49
+
50
+ m = number.numerator
51
+ n = number.denominator
52
+
53
+ return "I" if m == n
54
+
55
+ "".tap do |string|
56
+ while m != n && string.length < limit
57
+ if m < n
58
+ string << "L"
59
+ n = n - m
60
+ else
61
+ string << "R"
62
+ m = m - n
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ # @return [Array] pair of numbers less and greater than the provided number by provided difference
69
+ # @example
70
+ # FractionTree::Node.plus_minus(3, 2) => [1, 5]
71
+ # @param
72
+ # num the base
73
+ # diff the number subtracted and added to base
74
+ #
75
+ def plus_minus(num, diff)
76
+ [num - diff, num + diff]
77
+ end
78
+
79
+ # @return [Integer] the decimal power of the provided number
80
+ # @example
81
+ # FractionTree::Node.decimal_power(1000) => 3
82
+ # @param
83
+ # logarithmand the number from which the log base 10 is obtained
84
+ #
85
+ def decimal_power(logarithmand)
86
+ Math.log10(logarithmand.abs).floor
87
+ end
88
+ end
89
+
90
+ # @return [Array] set of fraction tree nodes leading to the given number
91
+ # @example
92
+ # FractionTree.node(7/4r).path
93
+ # => [(0/1), (1/0), (1/1), (2/1), (3/2), (5/3), (7/4)]
94
+ # @param limit of nodes to generate
95
+ #
96
+ def path(limit: Float::INFINITY)
97
+ return nil if infinite? || zero?
98
+
99
+ ln = tree.node(FractionTree.left_node)
100
+ rn = tree.node(FractionTree.right_node)
101
+ mn = ln + rn
102
+ return [ln, rn, mn] if mn == tree.node(number)
103
+
104
+ result = IDENTITY_MATRIX
105
+ m = numerator
106
+ n = denominator
107
+ [].tap do |p|
108
+ p << ln << rn << mn
109
+ while m != n && p.length < limit
110
+ if m < n
111
+ result = result * LEFT_MATRIX
112
+ n = n - m
113
+ else
114
+ result = result * RIGHT_MATRIX
115
+ m = m - n
116
+ end
117
+ p << tree.node(Rational(result.row(1).sum,result.row(0).sum))
118
+ end
119
+ end
120
+ end
121
+
122
+ # @return [Array] a pair of fraction tree nodes leading to the given number.
123
+ # @example
124
+ # FractionTree.node(15/13r).parents => [(8/7), (7/6)]
125
+ # FractionTree.node(Math::PI).parents => [(1181999955934188/376242271442657), (1959592697655605/623757728557343)]
126
+ #
127
+ def parents
128
+ tmp = path
129
+ [tmp[-2], tmp[-2..-1].inject(&:-)].sort
130
+ end
131
+
132
+ # @return [Array] of [FractionTree::Node], sequence of Farey neighbors to self. A Farey neighbor is a number c/d, who's relationship to a/b is such that ad − bc = 1, when c/d < a/b and bc − ad = 1 when c/d > a/b.
133
+ # @example
134
+ # FractionTree.node(3/2r).neighbors(10)
135
+ # => [(1/1), (2/1), (4/3), (5/3), (7/5), (8/5), (10/7), (11/7), (13/9), (14/9)]
136
+ # @param r range of harmonic series to search
137
+ #
138
+ def neighbors(r = 10**(self.class.decimal_power(number.numerator)+2))
139
+ ratio = number.to_r
140
+ denominator = ratio.denominator
141
+
142
+ [].tap do |collection|
143
+ (1..r-1).each do |i|
144
+ lower, upper = self.class.plus_minus(ratio, Rational(1,i*denominator))
145
+ collection << tree.node(lower) if tree.neighbors?(ratio, lower)
146
+ collection << tree.node(upper) if tree.neighbors?(ratio, upper)
147
+ end
148
+ end
149
+ end
150
+
151
+ # @return [Array] the ancestors shared by self and the given number
152
+ # @example
153
+ # FractionTree.node(4/3r).common_ancestors_with(7/4r)
154
+ # => [(0/1), (1/0), (1/1), (2/1), (3/2)]
155
+ #
156
+ # @param num [Numeric] other number sharing descendants with self
157
+ #
158
+ def common_ancestors_with(num)
159
+ path & tree.node(num).path
160
+ end
161
+
162
+ # @return [Array] of fraction tree nodes, descending from parents of number
163
+ # @example
164
+ # FractionTree.node(5/4r).descendancy_from(depth: 3)
165
+ # => [(1/1), (7/6), (6/5), (11/9), (5/4), (14/11), (9/7), (13/10), (4/3)]
166
+ #
167
+ # @param depth [Integer] how many nodes to collect
168
+ #
169
+ def descendancy_from(depth: 5)
170
+ (parent1, parent2) = parents
171
+ tree.descendants_of(parent1.number, parent2.number, depth:)
172
+ end
173
+
174
+ # @return [FractionTree::Node] child of self and given number
175
+ # @example
176
+ # FractionTree.node(5/4r).child_with(4/3r)
177
+ # => (9/7)
178
+ # @note return nil if bc - ad |= 1, for a/b, c/d
179
+ #
180
+ def child_with(num)
181
+ tree.child_of(number, num)
182
+ end
183
+
184
+ # @return [String] encoding of self
185
+ # @example
186
+ # FractionTree.node(Math.log2(5/4r)).encoding(limit: 30) => "LLLRRRRRRRRRLLRRLLLLRRRRRRLLRL"
187
+ # @param limit of codes to generate
188
+ #
189
+ def encoding(limit: Float::INFINITY)
190
+ self.class.encode(number, limit:)
191
+ end
192
+
193
+ # @return [FractionTree::Node] sum of self and another node
194
+ # @example
195
+ # FractionTree.node(5/4r) + FractionTree.node(3/2r)
196
+ # => (4/3)
197
+ #
198
+ def +(rhs)
199
+ tree.node(Rational(self.numerator+rhs.numerator, self.denominator+rhs.denominator))
200
+ end
201
+
202
+ # @return [FractionTree::Node] difference of self and another node
203
+ # @example
204
+ # FractionTree.node(5/4r) - FractionTree.node(3/2r)
205
+ # => (1/1)
206
+ #
207
+ def -(rhs)
208
+ tree.node(Rational((self.numerator-rhs.numerator).abs, (self.denominator-rhs.denominator).abs))
209
+ end
210
+
211
+ def inspect
212
+ "(#{numerator}/#{denominator})"
213
+ end
214
+ alias :to_s :inspect
215
+
216
+ def <=>(rhs)
217
+ self.number <=> rhs.number
218
+ end
219
+
220
+ def ==(rhs)
221
+ self.number == rhs.number
222
+ end
223
+
224
+ # Needed for intersection operations to work.
225
+ # https://blog.mnishiguchi.com/ruby-intersection-of-object-arrays
226
+ # https://shortrecipes.blogspot.com/2006/10/ruby-intersection-of-two-arrays-of.html
227
+ # Also, allows using with Set, which uses Hash as storage and equality of its elements is determined according to Object#eql? and Object#hash.
228
+ #
229
+ def eql?(rhs)
230
+ rhs.instance_of?(self.class) && number == rhs.number
231
+ end
232
+
233
+ def hash
234
+ p, q = 17, 37
235
+ p = q * @id.hash
236
+ p = q * @name.hash
237
+ end
238
+
239
+ private
240
+ def tree
241
+ FractionTree
242
+ end
243
+
244
+ def fraction_pair(number)
245
+ if number.infinite?
246
+ [1, 0]
247
+ else
248
+ [number.numerator, number.denominator]
249
+ end
250
+ end
251
+ end
252
+ end
data/lib/fraction_tree.rb CHANGED
@@ -1,325 +1,7 @@
1
+ require "matrix"
2
+ require "forwardable"
1
3
  require "bigdecimal/util"
2
4
  require "continued_fractions"
3
-
4
- # @author Jose Hales-Garcia
5
- #
6
- class FractionTree
7
- DEFAULT_TREE_DEPTH = 20
8
-
9
- class << self
10
- # @return [Array] the boundary nodes of the tree
11
- # @example
12
- # FractionTree.base_segment => [(0/1), (1/0)]
13
- #
14
- def base_segment
15
- [Node.new(0,1), Node.new(1,0)]
16
- end
17
-
18
- # @return [Array] a multi-dimensional array with the elements of fraction tree, organized by level/row
19
- # @example
20
- # FractionTree.tree(4)
21
- # => [[(0/1), (1/0)],
22
- # [(1/1)],
23
- # [(1/2), (2/1)],
24
- # [(1/3), (2/3), (3/2), (3/1)]]
25
- #
26
- # @param number [Integer] the depth of the tree
27
- #
28
- def tree(depth=DEFAULT_TREE_DEPTH)
29
- Array.new(depth, 0).tap do |sbt|
30
- row = 0
31
- sbt[row] = base_segment
32
- i = 2
33
- while i <= depth do
34
- figure_from = sbt[0..row].flatten.sort
35
- new_frow = Array.new(2**(i-2), 0)
36
- idx = 0
37
- figure_from.each_cons(2) do |left,right|
38
- new_frow[idx] = Node.new(left.numerator+right.numerator, left.denominator+right.denominator)
39
- idx += 1
40
- end
41
- row += 1
42
- sbt[row] = new_frow
43
- i += 1
44
- end
45
- end
46
- end
47
-
48
- # @return [Array] a sequence of fraction tree nodes
49
- # @example
50
- # FractionTree.sequence(3)
51
- # => [(0/1), (1/3), (1/2), (2/3), (1/1), (3/2), (2/1), (3/1), (1/0)]
52
- #
53
- # @param depth [Integer] the number of iterations of the algorithm to run. The number of nodes returned will be greater
54
- # @param segment [Array] a tuple array of [FractionTree::Node] defining the segment of the tree to collect nodes.
55
- #
56
- def sequence(depth=5, segment: base_segment)
57
- [segment.first]+_sequence(depth, segment:)+[segment.last]
58
- end
59
-
60
- # @return [Array] set of fraction nodes leading to the given number
61
- # @example
62
- # FractionTree.path_to(7/4r) => [(1/1), (2/1), (3/2), (5/3), (7/4)]
63
- #
64
- # @param number [Rational] the target the fraction path leads to
65
- # @param find_parents [Boolean] list all ancestors or only immediate parents
66
- # @param segment [Array] a tuple of [FractionTree::Node], defining the segment's starting left and right boundaries
67
- #
68
- def path_to(number, find_parents: false, segment: base_segment)
69
- return Node.new(number.numerator, number.denominator) if number.zero?
70
- number = number.kind_of?(Float) ? number.to_d : number
71
-
72
- q = Node.new(number.numerator, number.denominator)
73
- l = segment.first
74
- h = segment.last
75
- not_found = true
76
- parents = []
77
- results = []
78
- while not_found
79
- m = (l + h)
80
- if m < q
81
- l = m
82
- elsif m > q
83
- h = m
84
- else
85
- parents << l << h
86
- not_found = false
87
- end
88
- results << m
89
- end
90
- find_parents == false ? results : parents
91
- end
92
-
93
- # @return [Array] a pair of fraction tree nodes leading to the given number.
94
- # For irrational numbers, the parent nodes are one of an infinite series, whose nearness is determined by the limits of the system
95
- # @example
96
- # FractionTree.parents_of(15/13r) => [(8/7), (7/6)]
97
- # FractionTree.parents_of(Math::PI) => [(447288330638589/142376297616907), (436991388364966/139098679093749)]
98
- #
99
- # @param number [Rational] the child number whose parents are being sought
100
- #
101
- def parents_of(number)
102
- path_to(number, find_parents: true)
103
- end
104
-
105
- # @return [Array] the ancestors shared by the given descendants
106
- # @example
107
- # FractionTree.common_ancestors_between(4/3r, 7/4r)
108
- # => [(1/1), (2/1), (3/2)]
109
- #
110
- # @param number1 [Rational] one of two descendants
111
- # @param number2 [Rational] two of two descendants
112
- #
113
- def common_ancestors_between(number1, number2)
114
- path_to(number1) & path_to(number2)
115
- end
116
-
117
- # @return [Array] the descendants of number starting at its parents
118
- # @example
119
- # FractionTree.descendancy_from(5/4r, 3)
120
- # => [(1/1), (7/6), (6/5), (11/9), (5/4), (14/11), (9/7), (13/10), (4/3)]
121
- #
122
- # @param number [Rational] around which descendancy is focused
123
- # @param depth [Integer] how many nodes to collect
124
- #
125
- def descendancy_from(number, depth=5)
126
- parent1, parent2 = parents_of(number)
127
- descendants_of(parent1, parent2, depth)
128
- end
129
-
130
- # @return [FractionTree::Node] the mediant child of the given numbers
131
- # Return nil if bc - ad |= 1, for a/b, c/d
132
- # @example
133
- # FractionTree.child_of(1/1r, 4/3r) => (5/4)
134
- # FractionTree.child_of(7/4r, 4/3r) => nil
135
- #
136
- # @param number1 [Rational] one of two parents
137
- # @param number2 [Rational] two of two parents
138
- # @param strict_neighbors [Boolean] whether to apply the strict Farey tree neighbor requirement
139
- #
140
- def child_of(number1, number2, strict_neighbors: true)
141
- return nil unless farey_neighbors?(number1, number2) || !strict_neighbors
142
- Node.new(number1.numerator, number1.denominator) + Node.new(number2.numerator, number2.denominator)
143
- end
144
-
145
- # @return [Array] of nodes descended from parent1 and parent2
146
- # Return empty array if bc - ad |= 1, for a/b, c/d
147
- # @example
148
- # FractionTree.descendants_of(1/1r, 4/3r, 3)
149
- # => [(1/1), (7/6), (6/5), (11/9), (5/4), (14/11), (9/7), (13/10), (4/3)]
150
- #
151
- # @param parent1 [Rational] one of two parents
152
- # @param parent2 [Rational] two of two parents
153
- # @param depth [Integer] the depth to collect
154
- # @param strict_neighbors [Boolean] whether to apply the strict Farey tree neighbor requirement
155
- #
156
- def descendants_of(parent1, parent2, depth=5, strict_neighbors: true)
157
- return [] unless farey_neighbors?(parent1, parent2) || !strict_neighbors
158
- segment = [Node.new(parent1.numerator, parent1.denominator), Node.new(parent2.numerator, parent2.denominator)]
159
- sequence(depth, segment: segment)
160
- end
161
-
162
- # @return [Array] of FractionTree::Nodes leading quotient-wise to number
163
- # @example
164
- # FractionTree.quotient_walk(15/13r)
165
- # => [(1/1), (2/1), (3/2), (4/3), (5/4), (6/5), (7/6), (8/7), (15/13)]
166
- #
167
- # @param number [Numeric] to walk toward
168
- # @param limit [Integer] the depth of the walk. Useful for irrational numbers
169
- # @param segment [Array] the tuple of [FractionTree::Node] defining the segment of the tree
170
- #
171
- def quotient_walk(number, limit: 10, segment: computed_base_segment(number))
172
- iterating_quotients = ContinuedFraction.new(number, limit).quotients.drop(1)
173
- comparing_node = Node.new(number.numerator, number.denominator)
174
-
175
- segment.tap do |arr|
176
- held_node = arr[-2]
177
- moving_node = arr[-1]
178
-
179
- iterating_quotients.each do |q|
180
- (1..q).each do |i|
181
- arr << held_node + moving_node
182
- # We don't want to walk past the number when it's a rational number and we've reached it
183
- break if arr.include?(comparing_node)
184
- moving_node = arr[-1]
185
- end
186
- held_node = arr[-2]
187
- end
188
- end
189
- end
190
-
191
- # @return [Array] of numerators of the fraction tree nodes. Also known as the Stern-Brocot sequence.
192
- # @example
193
- # FractionTree.numeric_sequence.take(12)
194
- # => [1, 1, 2, 1, 3, 2, 3, 1, 4, 3, 5, 2]
195
- #
196
- def numeric_sequence
197
- return enum_for :numeric_sequence unless block_given?
198
- a=[1,1]
199
-
200
- 0.step do |i|
201
- yield a[i]
202
- a << a[i]+a[i+1] << a[i+1]
203
- end
204
- end
205
-
206
- # @return [Array] of Farey neighbors to the given number. A Farey neighbor is a number b/c, who's relationship to a/b is such that ad − bc = 1, when c/d < a/b and bc − ad = 1 when c/d > a/b.
207
- # @example
208
- # FractionTree.farey_neighbors(3/2r, 10)
209
- # => [(1/1), (2/1), (4/3), (5/3), (7/5), (8/5), (10/7), (11/7), (13/9), (14/9)]
210
- # @param number with neighbors
211
- # @param range of harmonic series to search
212
- #
213
- def farey_neighbors(number, range = 10**(decimal_power(number.numerator)+2))
214
- ratio = number.to_r
215
- denominator = ratio.denominator
216
-
217
- [].tap do |collection|
218
- (1..range-1).each do |i|
219
- lower, upper = plus_minus(ratio, Rational(1,i*denominator))
220
- collection << lower if farey_neighbors?(ratio, lower)
221
- collection << upper if farey_neighbors?(ratio, upper)
222
- end
223
- end
224
- end
225
-
226
- # @return [Boolean] whether two numbers are Farey neighbors
227
- # @example
228
- # FractionTree.farey_neighbors?(3/2r, 4/3r) => true
229
- # FractionTree.farey_neighbors?(3/2r, 7/4r) => false
230
- # @param num1 of comparison
231
- # @param num2 of comparison
232
- #
233
- def farey_neighbors?(num1, num2)
234
- (num1.numerator * num2.denominator - num1.denominator * num2.numerator).abs == 1
235
- end
236
-
237
- private
238
- def computed_base_segment(number)
239
- floor = number.floor
240
- [Node.new(floor,1), Node.new(floor+1,1)]
241
- end
242
-
243
- def _sequence(depth = 5, segment:)
244
- return [] if depth == 0
245
-
246
- mediant = segment.first + segment.last
247
-
248
- # Generate left segment, mediant, then right segment
249
- _sequence(depth - 1, segment: [segment.first, mediant]) + [mediant] + _sequence(depth - 1, segment: [mediant, segment.last])
250
- end
251
-
252
- def plus_minus(number, diff)
253
- [number - diff, number + diff]
254
- end
255
-
256
- def decimal_power(number)
257
- Math.log10(number.abs).floor
258
- end
259
- end
260
-
261
- class Node
262
- include Comparable
263
-
264
- attr_reader :numerator, :denominator, :weight
265
-
266
- def initialize(n,d)
267
- @numerator = n
268
- @denominator = d
269
- @weight = (d == 0 ? Float::INFINITY : Rational(@numerator, @denominator))
270
- end
271
-
272
- alias :to_r :weight
273
-
274
- def inspect
275
- "(#{numerator}/#{denominator})"
276
- end
277
- alias :to_s :inspect
278
-
279
- def <=>(rhs)
280
- self.weight <=> rhs.weight
281
- end
282
-
283
- def ==(rhs)
284
- self.weight == rhs.weight
285
- end
286
-
287
- # Needed for intersection operations to work.
288
- # https://blog.mnishiguchi.com/ruby-intersection-of-object-arrays
289
- # https://shortrecipes.blogspot.com/2006/10/ruby-intersection-of-two-arrays-of.html
290
- # Also, allows using with Set, which uses Hash as storage and equality of its elements is determined according to Object#eql? and Object#hash.
291
- #
292
- def eql?(rhs)
293
- rhs.instance_of?(self.class) && weight == rhs.weight
294
- end
295
-
296
- def hash
297
- p, q = 17, 37
298
- p = q * @id.hash
299
- p = q * @name.hash
300
- end
301
-
302
- def +(rhs)
303
- self.class.new(self.numerator+rhs.numerator, self.denominator+rhs.denominator)
304
- end
305
- end
306
- end
307
-
308
- class SternBrocotTree < FractionTree; end
309
- class ScaleTree < FractionTree; end
310
-
311
- class OctaveReducedTree < FractionTree
312
- def self.base_segment
313
- [Node.new(1,1), Node.new(2,1)]
314
- end
315
- end
316
-
317
- class FareyTree < FractionTree
318
- def self.base_segment
319
- [Node.new(0,1), Node.new(1,1)]
320
- end
321
- end
322
-
323
- class KeyboardTree < FareyTree; end
324
- class ScaleStepTree < FareyTree; end
325
- class Log2Tree < FareyTree; end
5
+ require_relative "fraction_tree/node"
6
+ require_relative "fraction_tree/extensions"
7
+ require_relative "fraction_tree/fraction_tree"
metadata CHANGED
@@ -1,15 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fraction-tree
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jose Hales-Garcia
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-01-16 00:00:00.000000000 Z
11
+ date: 2024-09-26 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: matrix
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: forwardable
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
13
41
  - !ruby/object:Gem::Dependency
14
42
  name: continued_fractions
15
43
  requirement: !ruby/object:Gem::Requirement
@@ -87,6 +115,9 @@ extensions: []
87
115
  extra_rdoc_files: []
88
116
  files:
89
117
  - lib/fraction_tree.rb
118
+ - lib/fraction_tree/extensions.rb
119
+ - lib/fraction_tree/fraction_tree.rb
120
+ - lib/fraction_tree/node.rb
90
121
  homepage: https://jolohaga.github.io/fraction-tree/
91
122
  licenses:
92
123
  - MIT
@@ -107,7 +138,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
107
138
  - !ruby/object:Gem::Version
108
139
  version: '3.1'
109
140
  requirements: []
110
- rubygems_version: 3.5.3
141
+ rubygems_version: 3.5.20
111
142
  signing_key:
112
143
  specification_version: 4
113
144
  summary: Fraction tree