fraction-tree 1.1.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9d4c2e3c84acc2fa31151b2863bf7c602490e891004198884e8002a7b7f3cc32
4
- data.tar.gz: c335d6bfb3f54c8471835cbc9283d904f8d71f7786cfde02a41448a1840ecb02
3
+ metadata.gz: afa73aeb4956a309b6e03e8d58426e407164e4697280b1d27f672a4e73963eb5
4
+ data.tar.gz: a643a87bd78aee13fe93d5d0e28322b5cfbc5c46b67e7617e0e217160461f635
5
5
  SHA512:
6
- metadata.gz: ef4347b24418118dbf6f4c8fdc6ac03829829aaf5c15d51295befda2f6bddad1b7022b7b428906998ab2a18106a68850dacdc61c3616a345d911f5db32008c86
7
- data.tar.gz: 940a05ae9a163d32cae321457da5135334f8a45a96bd0a6141d420d133216c912cd50826c33c3488fa7c17c96286ec11e755e2c8199afe4cddc8fee9846e25bf
6
+ metadata.gz: 3dca9f6465ebbc4ce801c48f7dec3fa8e1e2ed98742afd997b6cce5e7c2dcb46588ce49e9f786c9ac5166b20e765f6d0be94844ec1fb85a7661f9c19ce3ef29c
7
+ data.tar.gz: bb93ac7938caa3d50950295449e386e4eed308fb0d1d4228ed9e62139e210017174c03c3ad1840bb983f7d46c45c93bd33a4c7b3ae9d2bbed667c5523d9693b0
@@ -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,249 @@
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
+ #
46
+ def encode(number)
47
+ return nil if (number.infinite? || number.zero?)
48
+
49
+ m = number.numerator
50
+ n = number.denominator
51
+
52
+ return "I" if m == n
53
+
54
+ "".tap do |string|
55
+ while m != n
56
+ if m < n
57
+ string << "L"
58
+ n = n - m
59
+ else
60
+ string << "R"
61
+ m = m - n
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ # @return [Array] pair of numbers less and greater than the provided number by provided difference
68
+ # @example
69
+ # FractionTree::Node.plus_minus(3, 2) => [1, 5]
70
+ # @param
71
+ # num the base
72
+ # diff the number subtracted and added to base
73
+ #
74
+ def plus_minus(num, diff)
75
+ [num - diff, num + diff]
76
+ end
77
+
78
+ # @return [Integer] the decimal power of the provided number
79
+ # @example
80
+ # FractionTree::Node.decimal_power(1000) => 3
81
+ # @param
82
+ # logarithmand the number from which the log base 10 is obtained
83
+ #
84
+ def decimal_power(logarithmand)
85
+ Math.log10(logarithmand.abs).floor
86
+ end
87
+ end
88
+
89
+ # @return [Array] set of fraction tree nodes leading to the given number
90
+ # @example
91
+ # FractionTree.node(7/4r).path
92
+ # => [(0/1), (1/0), (1/1), (2/1), (3/2), (5/3), (7/4)]
93
+ #
94
+ def path
95
+ return nil if infinite? || zero?
96
+
97
+ ln = tree.node(FractionTree.left_node)
98
+ rn = tree.node(FractionTree.right_node)
99
+ mn = ln + rn
100
+ return [ln, rn, mn] if mn == tree.node(number)
101
+
102
+ result = IDENTITY_MATRIX
103
+ m = numerator
104
+ n = denominator
105
+ [].tap do |p|
106
+ p << ln << rn << mn
107
+ while m != n
108
+ if m < n
109
+ result = result * LEFT_MATRIX
110
+ n = n - m
111
+ else
112
+ result = result * RIGHT_MATRIX
113
+ m = m - n
114
+ end
115
+ p << tree.node(Rational(result.row(1).sum,result.row(0).sum))
116
+ end
117
+ end
118
+ end
119
+
120
+ # @return [Array] a pair of fraction tree nodes leading to the given number.
121
+ # @example
122
+ # FractionTree.node(15/13r).parents => [(8/7), (7/6)]
123
+ # FractionTree.node(Math::PI).parents => [(1181999955934188/376242271442657), (1959592697655605/623757728557343)]
124
+ #
125
+ def parents
126
+ tmp = path
127
+ [tmp[-2], tmp[-2..-1].inject(&:-)].sort
128
+ end
129
+
130
+ # @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.
131
+ # @example
132
+ # FractionTree.node(3/2r).neighbors(10)
133
+ # => [(1/1), (2/1), (4/3), (5/3), (7/5), (8/5), (10/7), (11/7), (13/9), (14/9)]
134
+ # @param r range of harmonic series to search
135
+ #
136
+ def neighbors(r = 10**(self.class.decimal_power(number.numerator)+2))
137
+ ratio = number.to_r
138
+ denominator = ratio.denominator
139
+
140
+ [].tap do |collection|
141
+ (1..r-1).each do |i|
142
+ lower, upper = self.class.plus_minus(ratio, Rational(1,i*denominator))
143
+ collection << tree.node(lower) if tree.neighbors?(ratio, lower)
144
+ collection << tree.node(upper) if tree.neighbors?(ratio, upper)
145
+ end
146
+ end
147
+ end
148
+
149
+ # @return [Array] the ancestors shared by self and the given number
150
+ # @example
151
+ # FractionTree.node(4/3r).common_ancestors_with(7/4r)
152
+ # => [(0/1), (1/0), (1/1), (2/1), (3/2)]
153
+ #
154
+ # @param num [Numeric] other number sharing descendants with self
155
+ #
156
+ def common_ancestors_with(num)
157
+ path & tree.node(num).path
158
+ end
159
+
160
+ # @return [Array] of fraction tree nodes, descending from parents of number
161
+ # @example
162
+ # FractionTree.node(5/4r).descendancy_from(depth: 3)
163
+ # => [(1/1), (7/6), (6/5), (11/9), (5/4), (14/11), (9/7), (13/10), (4/3)]
164
+ #
165
+ # @param depth [Integer] how many nodes to collect
166
+ #
167
+ def descendancy_from(depth: 5)
168
+ (parent1, parent2) = parents
169
+ tree.descendants_of(parent1.number, parent2.number, depth:)
170
+ end
171
+
172
+ # @return [FractionTree::Node] child of self and given number
173
+ # @example
174
+ # FractionTree.node(5/4r).child_with(4/3r)
175
+ # => (9/7)
176
+ # @note return nil if bc - ad |= 1, for a/b, c/d
177
+ #
178
+ def child_with(num)
179
+ tree.child_of(number, num)
180
+ end
181
+
182
+ # @return [String] encoding of self
183
+ # @example
184
+ # FractionTree.node(5/4r).encoding => "RLLL"
185
+ #
186
+ def encoding
187
+ @encoding ||= self.class.encode(number)
188
+ end
189
+
190
+ # @return [FractionTree::Node] sum of self and another node
191
+ # @example
192
+ # FractionTree.node(5/4r) + FractionTree.node(3/2r)
193
+ # => (4/3)
194
+ #
195
+ def +(rhs)
196
+ tree.node(Rational(self.numerator+rhs.numerator, self.denominator+rhs.denominator))
197
+ end
198
+
199
+ # @return [FractionTree::Node] difference of self and another node
200
+ # @example
201
+ # FractionTree.node(5/4r) - FractionTree.node(3/2r)
202
+ # => (1/1)
203
+ #
204
+ def -(rhs)
205
+ tree.node(Rational((self.numerator-rhs.numerator).abs, (self.denominator-rhs.denominator).abs))
206
+ end
207
+
208
+ def inspect
209
+ "(#{numerator}/#{denominator})"
210
+ end
211
+ alias :to_s :inspect
212
+
213
+ def <=>(rhs)
214
+ self.number <=> rhs.number
215
+ end
216
+
217
+ def ==(rhs)
218
+ self.number == rhs.number
219
+ end
220
+
221
+ # Needed for intersection operations to work.
222
+ # https://blog.mnishiguchi.com/ruby-intersection-of-object-arrays
223
+ # https://shortrecipes.blogspot.com/2006/10/ruby-intersection-of-two-arrays-of.html
224
+ # Also, allows using with Set, which uses Hash as storage and equality of its elements is determined according to Object#eql? and Object#hash.
225
+ #
226
+ def eql?(rhs)
227
+ rhs.instance_of?(self.class) && number == rhs.number
228
+ end
229
+
230
+ def hash
231
+ p, q = 17, 37
232
+ p = q * @id.hash
233
+ p = q * @name.hash
234
+ end
235
+
236
+ private
237
+ def tree
238
+ FractionTree #self.class.tree
239
+ end
240
+
241
+ def fraction_pair(number)
242
+ if number.infinite?
243
+ [1, 0]
244
+ else
245
+ [number.numerator, number.denominator]
246
+ end
247
+ end
248
+ end
249
+ 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.0.1
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-08-31 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.18
111
142
  signing_key:
112
143
  specification_version: 4
113
144
  summary: Fraction tree