fraction-tree 1.0.2 → 2.0.1

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