more_math 1.5.0 → 1.6.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.
@@ -1,13 +1,38 @@
1
1
  require 'more_math/ranking_common'
2
2
 
3
3
  module MoreMath
4
+ # A permutation represents a rearrangement of elements in a set. Permutations
5
+ # are ranked in lexicographic order, allowing efficient enumeration and
6
+ # indexing.
7
+ #
8
+ # @example Create a permutation of size 3
9
+ # perm = Permutation.new(3)
10
+ # # => #<Permutation:0x6ae34 @last=5, @rank=0, @size=3>
11
+ #
12
+ # @example Create a permutation with specific rank
13
+ # perm = Permutation.new(3, 5)
14
+ # # => #<Permutation:0x6ae34 @last=5, @rank=5, @size=3>
15
+ #
16
+ # @example Get the permutation as an array of indices
17
+ # perm = Permutation.new(3, 5)
18
+ # perm.value
19
+ # # => [2, 1, 0]
20
+ #
21
+ # @example Project onto actual data
22
+ # perm = Permutation.new(3, 5)
23
+ # perm.project(['a', 'b', 'c'])
24
+ # # => ['c', 'b', 'a']
4
25
  class Permutation
5
26
  include Enumerable
6
27
  include Comparable
7
28
  include RankingCommon
8
29
 
9
- # Creates a new Permutation instance of <code>size</code>
10
- # (and ranked with <code>rank</code>).
30
+ # Creates a new Permutation instance of <code>size</code> (and ranked with
31
+ # <code>rank</code>). Creates a new Permutation instance of size (and
32
+ # ranked with rank).
33
+ #
34
+ # @param size [Integer] The size of the permutation
35
+ # @param rank [Integer] The rank of the permutation (default: 0)
11
36
  def initialize(size, rank = 0)
12
37
  @size, self.rank = size, rank
13
38
  @last = factorial(size) - 1
@@ -17,6 +42,9 @@ module MoreMath
17
42
  # <code>indices</code>, that should consist of a permutation of Fixnums
18
43
  # in the range of <code>0</code> and <code>indices.size - 1</code>. This is
19
44
  # for example the result of a call to the Permutation#value method.
45
+ #
46
+ # @param indices [Array<Integer>] array of indices representing a permutation
47
+ # @return [Permutation] new permutation instance
20
48
  def self.from_value(indices)
21
49
  indices = indices.clone
22
50
  obj = new(indices.size)
@@ -29,6 +57,10 @@ module MoreMath
29
57
  # Creates a new Permutation instance from the Array of Arrays
30
58
  # <code>cycles</code>. This is for example the result of a
31
59
  # call to the Permutation#cycles method .
60
+ #
61
+ # @param cycles [Array<Array<Integer>>] array of cycles
62
+ # @param max [Integer] maximum element index (default: 0)
63
+ # @return [Permutation] new permutation instance
32
64
  def self.from_cycles(cycles, max = 0)
33
65
  indices = Array.new(max)
34
66
  cycles.each do |cycle|
@@ -45,6 +77,10 @@ module MoreMath
45
77
  # collection as the default Permutation#project data object. A
46
78
  # collection should respond to size, [], and []=. The Permutation
47
79
  # instance will default to rank 0 if none is given.
80
+ #
81
+ # @param collection [Object] collection to use for projection
82
+ # @param rank [Integer] the rank of this permutation (default: 0)
83
+ # @return [Permutation] new permutation instance
48
84
  def self.for(collection, rank = 0)
49
85
  perm = new(collection.size, rank)
50
86
  perm.instance_variable_set(:@collection, collection)
@@ -53,12 +89,17 @@ module MoreMath
53
89
 
54
90
  # Shortcut to generate the identity permutation that has
55
91
  # Permutation#size <code>n</code>.
92
+ #
93
+ # @param n [Integer] size of identity permutation
94
+ # @return [Permutation] identity permutation of size n
56
95
  def self.identity(n)
57
96
  new(n)
58
97
  end
59
98
 
60
99
  # Returns the identity permutation that has the same Permutation#size as this
61
100
  # instance.
101
+ #
102
+ # @return [Permutation] identity permutation of same size
62
103
  def identity
63
104
  self.class.new(size)
64
105
  end
@@ -67,6 +108,10 @@ module MoreMath
67
108
  # Both arguments must be the same length and must contain the same
68
109
  # elements. If these arrays contain duplicate elements, the solution
69
110
  # will not be unique.
111
+ #
112
+ # @param a [Array] initial array of elements
113
+ # @param b [Array] target array of elements
114
+ # @return [Permutation] permutation that maps a to b
70
115
  def self.for_mapping(a, b)
71
116
  a.size == b.size or
72
117
  raise ArgumentError, "Initial and final lists must be the same length"
@@ -83,6 +128,9 @@ module MoreMath
83
128
 
84
129
  # Computes the nth power (ie the nth repeated permutation) of this instance.
85
130
  # Negative powers are taken to be powers of the inverse.
131
+ #
132
+ # @param n [Integer] power to raise permutation to
133
+ # @return [Permutation] result of permutation raised to power n
86
134
  def power(n)
87
135
  if n.respond_to?(:to_int)
88
136
  n = n.to_int
@@ -102,6 +150,9 @@ module MoreMath
102
150
  # instance. That implies that the indices produced by a call to the
103
151
  # Permutation#value method of this instance is the permutation ranked with
104
152
  # this new <code>rank</code>.
153
+ #
154
+ # @param m [Integer] new rank value
155
+ # @return [Integer] assigned rank
105
156
  def rank=(m)
106
157
  @rank = m % factorial(size)
107
158
  end
@@ -114,6 +165,8 @@ module MoreMath
114
165
  # # => #<Permutation:0x6ae34 @last=719, @rank=312, @size=6>
115
166
  # perm.value
116
167
  # # => [2, 4, 0, 1, 3, 5]
168
+ #
169
+ # @return [Array<Integer>] array of indices representing this permutation
117
170
  def value
118
171
  unrank_indices(@rank)
119
172
  end
@@ -124,30 +177,47 @@ module MoreMath
124
177
  # with Permutation.for the collection used to create
125
178
  # it is used as a data object.
126
179
  #
127
- # <b>Example:</b>
128
- # perm = Permutation.new(6, 312)
129
- # # => #<Permutation:0x6ae34 @last=719, @rank=312, @size=6>
130
- # perm.project("abcdef")
131
- # # => "ceabdf"
180
+ # @example
181
+ # perm = Permutation.new(6, 312)
182
+ # # => #<Permutation:0x6ae34 @last=719, @rank=312, @size=6>
183
+ # perm.project("abcdef")
184
+ # # => "ceabdf"
185
+ #
186
+ # @param data [Object] data to project onto (optional)
187
+ # @return [Object] projected result
132
188
  def project(data = (@collection if defined? @collection))
133
189
  data or raise ArgumentError, "a collection is required to project"
134
190
  raise ArgumentError, "data size is != #{size}!" if data.size != size
135
- projection = data.clone
191
+ projection = data.dup
136
192
  value.each_with_index { |i, j| projection[j] = data[i] }
137
193
  projection
138
194
  end
139
195
 
140
- # Compares to Permutation instances according to their Permutation#size
141
- # and the Permutation#rank.
196
+ # Compare this permutation with another based on size and rank.
197
+ #
198
+ # This method implements the comparison operator for Permutation objects,
199
+ # allowing them to be sorted and compared using standard Ruby comparison
200
+ # operators. The comparison is first done by size, then by rank for permutations
201
+ # of the same size.
142
202
  #
143
- # The mixed in methods from the Comparable module rely on this method.
203
+ # @param other [Object] the other permutation to compare with
204
+ # @return [Integer] -1 if this permutation should be ordered before the other,
205
+ # 0 if they are equal, 1 if this permutation should be ordered after the other
144
206
  def <=>(other)
145
- size <=> other.size.zero? || rank <=> other.rank
207
+ sc = size <=> other.size
208
+ if sc.zero?
209
+ rank <=> other.rank
210
+ else
211
+ sc
212
+ end
146
213
  end
147
214
 
148
215
  # Returns true if this Permutation instance and the other have the same
149
216
  # value, that is both Permutation instances have the same Permutation#size
150
217
  # and the same Permutation#rank.
218
+ #
219
+ # @param other [Object] object to compare with
220
+ # @return [Boolean] true if equal
151
221
  def eql?(other)
152
222
  self.class == other.class && size == other.size && rank == other.rank
153
223
  end
@@ -155,12 +225,16 @@ module MoreMath
155
225
  alias == eql?
156
226
 
157
227
  # Computes a unique hash value for this Permutation instance.
228
+ #
229
+ # @return [Integer] hash value
158
230
  def hash
159
231
  size.hash ^ rank.hash
160
232
  end
161
233
 
162
- # Switchtes this Permutation instance to the inverted permutation.
234
+ # Switches this Permutation instance to the inverted permutation.
163
235
  # (See Permutation#compose for an example.)
236
+ #
237
+ # @return [Permutation] self after inversion
164
238
  def invert!
165
239
  indices = unrank_indices(rank)
166
240
  inverted = Array.new(size)
@@ -173,6 +247,8 @@ module MoreMath
173
247
 
174
248
  # Returns the inverted Permutation of this Permutation instance.
175
249
  # (See Permutation#compose for an example.)
250
+ #
251
+ # @return [Permutation] inverted permutation
176
252
  def invert
177
253
  clone.invert!
178
254
  end
@@ -184,16 +260,20 @@ module MoreMath
184
260
  # composed with it's inverted permutation yields
185
261
  # the identity permutation, the permutation with rank 0.
186
262
  #
187
- # <b>Example:</b>
188
- # p1 = Permutation.new(5, 42)
189
- # # => #<Permutation:0x75370 @last=119, @rank=42, @size=5>
190
- # p2 = p1.invert
191
- # # => #<Permutation:0x653d0 @last=119, @rank=51, @size=5>
192
- # p1.compose(p2)
193
- # => #<Permutation:0x639a4 @last=119, @rank=0, @size=5>
194
- # Or a little nicer to look at:
195
- # p1 * -p1
196
- # # => #<Permutation:0x62004 @last=119, @rank=0, @size=5>
263
+ # @example
264
+ # p1 = Permutation.new(5, 42)
265
+ # # => #<Permutation:0x75370 @last=119, @rank=42, @size=5>
266
+ # p2 = p1.invert
267
+ # # => #<Permutation:0x653d0 @last=119, @rank=51, @size=5>
268
+ # p1.compose(p2)
269
+ # => #<Permutation:0x639a4 @last=119, @rank=0, @size=5>
270
+ #
271
+ # # Or a little nicer to look at:
272
+ # p1 * -p1
273
+ # # => #<Permutation:0x62004 @last=119, @rank=0, @size=5>
274
+ #
275
+ # @param other [Permutation] permutation to compose with
276
+ # @return [Permutation] composed permutation
197
277
  def compose(other)
198
278
  size == other.size or raise ArgumentError,
199
279
  "permutations of unequal sizes cannot be composed!"
@@ -210,11 +290,13 @@ module MoreMath
210
290
  # The return value of this method can be used to create a
211
291
  # new Permutation instance with the Permutation.from_cycles method.
212
292
  #
213
- # <b>Example:</b>
293
+ # @example
214
294
  # perm = Permutation.new(7, 23)
215
295
  # # => #<Permutation:0x58541c @last=5039, @rank=23, @size=7>
216
296
  # perm.cycles
217
297
  # # => [[3, 6], [4, 5]]
298
+ #
299
+ # @return [Array<Array<Integer>>] array of cycles
218
300
  def cycles
219
301
  perm = value
220
302
  result = [[]]
@@ -246,6 +328,8 @@ module MoreMath
246
328
  # A permutation is odd if it can be represented by an odd number of
247
329
  # transpositions (cycles of length 2), or even if it can be represented of
248
330
  # an even number of transpositions.
331
+ #
332
+ # @return [Integer] 1 for even, -1 for odd
249
333
  def signum
250
334
  s = 1
251
335
  cycles.each do |c|
@@ -257,11 +341,15 @@ module MoreMath
257
341
  alias sgn signum
258
342
 
259
343
  # Returns true if this permutation is even, false otherwise.
344
+ #
345
+ # @return [Boolean] true if even permutation
260
346
  def even?
261
347
  signum == 1
262
348
  end
263
349
 
264
350
  # Returns true if this permutation is odd, false otherwise.
351
+ #
352
+ # @return [Boolean] true if odd permutation
265
353
  def odd?
266
354
  signum == -1
267
355
  end
@@ -270,13 +358,23 @@ module MoreMath
270
358
 
271
359
  @@fcache = [ 1 ]
272
360
 
361
+ # Computes the factorial of a number using memoization for efficiency. This
362
+ # method is used internally by the permutation ranking system to calculate
363
+ # the total number of permutations for a given size.
364
+ #
365
+ # @param n [Integer] the number to calculate factorial for
366
+ # @return [Integer] the factorial of n
273
367
  def factorial(n)
274
368
  @@fcache.size.upto(n) { |i| @@fcache[i] = i * @@fcache[i - 1] }
275
369
  @@fcache[n]
276
370
  end
277
371
 
278
372
  # Rank the indices of the permutation +p+. Beware that this method may
279
- # change its argument +p+.
373
+ # change its argument +p+. Rank the indices of the permutation p. Beware
374
+ # that this method may change its argument p.
375
+ #
376
+ # @param p [Array<Integer>] the permutation indices to rank
377
+ # @return [Integer] the rank of the permutation indices
280
378
  def rank_indices(p)
281
379
  result = 0
282
380
  for i in 0...size
@@ -288,8 +386,15 @@ module MoreMath
288
386
  result
289
387
  end
290
388
 
291
- # Unrank the rank +m+, that is create a permutation of the appropriate size
292
- # and rank as an array of indices and return it.
389
+ # Unranks the given index value into an array of permutation indices.
390
+ #
391
+ # This method converts a rank value back into its corresponding permutation
392
+ # indices representation. It's the inverse operation of the rank_indices
393
+ # method and is used internally by the permutation ranking system to
394
+ # reconstruct permutation arrays from their rank values.
395
+ #
396
+ # @param m [Integer] The rank value to unrank
397
+ # @return [Array<Integer>] Array of indices representing the permutation
293
398
  def unrank_indices(m)
294
399
  result = Array.new(size, 0)
295
400
  for i in 0...size
@@ -1,21 +1,34 @@
1
1
  module MoreMath
2
+ # Common functionality for ranking systems. This module provides basic
3
+ # ranking operations that are shared across different ranking implementations
4
+ # in the MoreMath library, including permutations and subsets.
2
5
  module RankingCommon
3
6
  # Returns the size of this instance's collection, a Fixnum.
7
+ #
8
+ # @return [Integer] the size of the collection
4
9
  attr_reader :size
5
10
 
6
11
  # Returns the rank of this instance, a Fixnum in the range
7
12
  # of 0 and #last.
13
+ #
14
+ # @return [Integer] the current rank
8
15
  attr_reader :rank
9
16
 
10
17
  # Returns the rank of the last ranked instance.
18
+ #
19
+ # @return [Integer] the maximum rank value for this size
11
20
  attr_reader :last
12
21
 
13
22
  # Returns the collection the #rank applies to if any was set, otherwise
14
- # retrurns nil.
23
+ # returns nil.
24
+ #
25
+ # @return [Object, nil] the associated collection or nil
15
26
  attr_reader :collection
16
27
 
17
28
  # Switches this instance to the next ranked instance. If this was the #last
18
- # instance it wraps around the first (<code>rank == 0</code>) instance.
29
+ # instance it wraps around to the first (<code>rank == 0</code>) instance.
30
+ #
31
+ # @return [self] returns self after incrementing rank
19
32
  def next!
20
33
  self.rank += 1
21
34
  self
@@ -25,6 +38,8 @@ module MoreMath
25
38
 
26
39
  # Returns the next ranked instance. If this instance is the #last instance
27
40
  # it returns the first (<code>rank == 0</code>) instance again.
41
+ #
42
+ # @return [self] a new instance with incremented rank
28
43
  def next
29
44
  clone.next!
30
45
  end
@@ -33,6 +48,8 @@ module MoreMath
33
48
 
34
49
  # Switches this instance to the previously ranked instance. If this was the
35
50
  # first instance it returns the last (<code>rank == #last</code>) instance.
51
+ #
52
+ # @return [self] returns self after decrementing rank
36
53
  def pred!
37
54
  self.rank -= 1
38
55
  self
@@ -40,11 +57,15 @@ module MoreMath
40
57
 
41
58
  # Returns the previously ranked instance. If this was the first instance it
42
59
  # returns the last (<code>rank == #last</code>) instance.
60
+ #
61
+ # @return [self] a new instance with decremented rank
43
62
  def pred
44
63
  clone.pred!
45
64
  end
46
65
 
47
66
  # Switches this instance to a randomly ranked instance.
67
+ #
68
+ # @return [self] returns self after setting random rank
48
69
  def random!
49
70
  new_rank = rand(last + 1).to_i
50
71
  self.rank = new_rank
@@ -52,6 +73,8 @@ module MoreMath
52
73
  end
53
74
 
54
75
  # Returns a randomly ranked instance.
76
+ #
77
+ # @return [self] a new instance with random rank
55
78
  def random
56
79
  clone.random!
57
80
  end
@@ -63,7 +86,10 @@ module MoreMath
63
86
  # step.
64
87
  #
65
88
  # The mixed in methods from the Enumerable module rely on this method.
66
- def each # :yields: perm
89
+ #
90
+ # @yield [instance] yields each instance in sequence
91
+ # @return [self] returns self after iteration
92
+ def each
67
93
  0.upto(last) do |r|
68
94
  klon = clone
69
95
  klon.rank = r
@@ -72,13 +98,15 @@ module MoreMath
72
98
  self
73
99
  end
74
100
 
75
- # Does something similar to #each. It doesn't create new
76
- # instances (less overhead) for every iteration step, but yields to a
77
- # modified self instead. This is useful if one only wants to call a
78
- # method on the yielded value and work with the result of this call. It's
79
- # not a good idea to put the yielded values in a data structure because all
80
- # of them will reference the same (this!) instance. If you want to do this
81
- # use #each.
101
+ # Does something similar to #each. It doesn't create new instances (less
102
+ # overhead) for every iteration step, but yields to a modified self
103
+ # instead. This is useful if one only wants to call a method on the yielded
104
+ # value and work with the result of this call. It's not a good idea to put
105
+ # the yielded values in a data structure because all of them will reference
106
+ # the same (this!) instance. If you want to do this use #each.
107
+ #
108
+ # @yield [instance] yields the current instance
109
+ # @return [self] returns self after iteration
82
110
  def each!
83
111
  old_rank = rank
84
112
  0.upto(last) do |r|
@@ -1,6 +1,31 @@
1
1
  module MoreMath
2
2
  class Sequence
3
+ # Module containing moving average calculation methods
4
+ #
5
+ # Provides simple moving average functionality for sequence analysis and
6
+ # time series data.
3
7
  module MovingAverage
8
+ # Calculates a simple moving average for the sequence
9
+ #
10
+ # A simple moving average is calculated by taking the arithmetic mean of
11
+ # a specified number of consecutive elements in the sequence.
12
+ #
13
+ # @example Basic usage
14
+ # sequence = Sequence.new([1, 2, 3, 4, 5])
15
+ # sequence.simple_moving_average(3) # => [2.0, 3.0, 4.0]
16
+ #
17
+ # @example With alias usage
18
+ # sequence = Sequence.new([1, 2, 3, 4, 5])
19
+ # sequence.moving_average(2) # => [1.5, 2.5, 3.5, 4.5]
20
+ #
21
+ # @param n [Integer] The window size for the moving average (must be >= 1)
22
+ # @return [Array<Float>] Array of moving averages, where each element is
23
+ # the mean of n consecutive elements from the original sequence
24
+ # @raise [ArgumentError] If n < 1 or n > number of elements in the sequence
25
+ #
26
+ # @note The result array will contain (elements.size - n + 1) elements
27
+ # @note Each moving average is calculated as the arithmetic mean of n
28
+ # consecutive elements
4
29
  def simple_moving_average(n)
5
30
  n < 1 and raise ArgumentError, 'n < 1, has to be >= 1'
6
31
  n <= @elements.size or raise ArgumentError,
@@ -15,6 +40,8 @@ module MoreMath
15
40
  end
16
41
  avg
17
42
  end
43
+
44
+ # Alias for {simple_moving_average}
18
45
  alias moving_average simple_moving_average
19
46
  end
20
47
  end
@@ -1,5 +1,31 @@
1
+ # Refinement module that adds sequence conversion capabilities to Object.
2
+ #
3
+ # This refinement extends the Object class with a +to_seq+ method that
4
+ # converts any enumerable object into a MoreMath::Sequence.
5
+ #
6
+ # @example Converting an array to a sequence
7
+ # using MoreMath::Sequence::Refinement
8
+ # [1, 2, 3, 4, 5].to_seq
9
+ # # => #<MoreMath::Sequence:0x00007f8b8c0b8a00>
10
+ #
11
+ # @example Converting other enumerables
12
+ # using MoreMath::Sequence::Refinement
13
+ # (1..5).to_seq
14
+ # # => #<MoreMath::Sequence:0x00007f8b8c0b8a00>
15
+ #
16
+ # @note This refinement must be activated with +using
17
+ # MoreMath::Sequence::Refinement+ before it can be used
18
+ # @note The resulting sequence is frozen and cannot be modified
1
19
  module MoreMath::Sequence::Refinement
2
20
  refine ::Object do
21
+ # Converts this object into a MoreMath::Sequence.
22
+ #
23
+ # This method iterates over the object (assuming it's enumerable) and
24
+ # creates a new Sequence instance containing all elements.
25
+ #
26
+ # @return [MoreMath::Sequence] A new sequence containing all elements
27
+ # @note This method works with any enumerable object
28
+ # @note The resulting sequence is frozen and cannot be modified
3
29
  def to_seq
4
30
  ary = []
5
31
  each { |x| ary << x }