fraction-tree 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []