philiprehberger-ring_buffer 0.4.0 → 0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8d3a987697f096aa8b9d085eeb0a4bcd6f83a5af1a0c2afcae477a6e789bbcdf
4
- data.tar.gz: 66b3843e018fbb2f2e02d72d6b727e7cae71a64c299ac7e4e29747aebc8cf351
3
+ metadata.gz: 296d9b4ccfe2ca3cacba6bf34911e142381b0f8e40d2cdf0ed8f23432ff957a3
4
+ data.tar.gz: cfe72d59c6b58f6d0be55488a17d1760c3914ecad2ed5a91bd2dc903c8d8c8a1
5
5
  SHA512:
6
- metadata.gz: 10d2a15e1fc6afeb68d4c8a760170eddbec9c833c3c552fb72d9c3ea72d9efe5c741c1f0beca5375592344ffbac699b27d49fd3c18f75988bbc0e289078eb7f4
7
- data.tar.gz: be638339a29c4492487f1aefca54a2235256b09a7a8f3c07eda3b72a7e5df29e9aea9f77d9e2a7225ef9c54735b6ab17a173e0b1153edb23549ca9e0a3b1f133
6
+ metadata.gz: 5268f470fe0f0ca1ba767f7e4c5c4f121686d2b6ae57a83b2aafae19600f88dc1dbef2b5a097001d407afeaa8d0e48613df80aafccf7cf745aff47b247fd8f7f
7
+ data.tar.gz: 00c612791299049a02e35df9c4d3ff9fe8e0106beb37408b351df13a8875b7ea40b53540d61139f98ec814122dbee73bee7a6c62ba5b571dd7c1bf0101d9fe77
data/CHANGELOG.md CHANGED
@@ -7,6 +7,19 @@ and this gem adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.6.0] - 2026-04-15
11
+
12
+ ### Added
13
+ - `#moving_average(window:)` for sliding window averages over buffer elements
14
+ - `#ema(alpha:)` for exponential moving average calculation
15
+
16
+ ## [0.5.0] - 2026-04-14
17
+
18
+ ### Added
19
+ - `#concat(*values)` to push multiple values at once
20
+ - `#percentile(p)` for arbitrary percentile calculation with linear interpolation
21
+ - `#sample(n)` to return random element(s) from the buffer
22
+
10
23
  ## [0.4.0] - 2026-04-14
11
24
 
12
25
  ### Added
@@ -78,6 +91,8 @@ and this gem adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
78
91
  - Last-n element retrieval
79
92
  - Enumerable support
80
93
 
94
+ [0.6.0]: https://github.com/philiprehberger/rb-ring-buffer/releases/tag/v0.6.0
95
+ [0.5.0]: https://github.com/philiprehberger/rb-ring-buffer/releases/tag/v0.5.0
81
96
  [0.4.0]: https://github.com/philiprehberger/rb-ring-buffer/releases/tag/v0.4.0
82
97
  [0.3.0]: https://github.com/philiprehberger/rb-ring-buffer/releases/tag/v0.3.0
83
98
  [0.2.0]: https://github.com/philiprehberger/rb-ring-buffer/releases/tag/v0.2.0
data/README.md CHANGED
@@ -117,6 +117,46 @@ buf.resize(2)
117
117
  buf.to_a # => [2, 3] (keeps most recent)
118
118
  ```
119
119
 
120
+ ### Batch Push
121
+
122
+ ```ruby
123
+ buf = Philiprehberger::RingBuffer.new(5)
124
+ buf.concat(1, 2, 3, 4, 5)
125
+ buf.to_a # => [1, 2, 3, 4, 5]
126
+ ```
127
+
128
+ ### Percentile
129
+
130
+ ```ruby
131
+ buf = Philiprehberger::RingBuffer.new(100)
132
+ (1..100).each { |v| buf.push(v) }
133
+
134
+ buf.percentile(25) # => 25.75
135
+ buf.percentile(50) # => 50.5
136
+ buf.percentile(75) # => 75.25
137
+ buf.percentile(90) # => 90.1
138
+ ```
139
+
140
+ ### Moving Averages
141
+
142
+ ```ruby
143
+ buf = Philiprehberger::RingBuffer.new(5)
144
+ [1, 2, 3, 4, 5].each { |v| buf.push(v) }
145
+
146
+ buf.moving_average(window: 3) # => [2.0, 3.0, 4.0]
147
+ buf.ema(alpha: 0.5) # => 4.0625
148
+ ```
149
+
150
+ ### Random Sampling
151
+
152
+ ```ruby
153
+ buf = Philiprehberger::RingBuffer.new(5)
154
+ [10, 20, 30, 40, 50].each { |v| buf.push(v) }
155
+
156
+ buf.sample # => random element
157
+ buf.sample(2) # => [random, random]
158
+ ```
159
+
120
160
  ### Enumerable
121
161
 
122
162
  ```ruby
@@ -144,7 +184,10 @@ buf.select(&:odd?) # => [1, 3]
144
184
  | `#first(n)` | First n elements (oldest) |
145
185
  | `#last(n)` | Last n elements (most recent) |
146
186
  | `#clear` | Remove all elements, reset state |
187
+ | `#concat(*values)` | Push multiple values at once |
147
188
  | `#resize(new_capacity)` | Change buffer capacity, keeping most recent elements |
189
+ | `#percentile(p)` | Calculate the p-th percentile (0-100) with interpolation |
190
+ | `#sample(n)` | Random element (or array of n random elements) |
148
191
  | `#inspect` | Human-readable string representation |
149
192
  | `#average` | Average of numeric elements |
150
193
  | `#sum` | Sum of numeric elements |
@@ -153,6 +196,8 @@ buf.select(&:odd?) # => [1, 3]
153
196
  | `#variance` | Population variance |
154
197
  | `#stddev` | Population standard deviation |
155
198
  | `#median` | Median value |
199
+ | `#moving_average(window:)` | Sliding window averages (oldest to newest) |
200
+ | `#ema(alpha:)` | Exponential moving average (single float) |
156
201
 
157
202
  ## Development
158
203
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Philiprehberger
4
4
  class RingBuffer
5
- VERSION = '0.4.0'
5
+ VERSION = '0.6.0'
6
6
  end
7
7
  end
@@ -244,6 +244,84 @@ module Philiprehberger
244
244
  self
245
245
  end
246
246
 
247
+ # Push multiple values at once
248
+ #
249
+ # @param values [Array<Object>] values to push
250
+ # @return [self]
251
+ def concat(*values)
252
+ values.each { |v| push(v) }
253
+ self
254
+ end
255
+
256
+ # Calculate the p-th percentile of numeric elements
257
+ #
258
+ # Uses linear interpolation between nearest ranks.
259
+ #
260
+ # @param p [Numeric] percentile (0-100)
261
+ # @return [Float, nil] the percentile value, or nil if empty
262
+ # @raise [Error] if p is outside 0-100
263
+ def percentile(p)
264
+ raise Error, 'percentile must be between 0 and 100' unless p.is_a?(Numeric) && p >= 0 && p <= 100
265
+ return nil if empty?
266
+
267
+ sorted = to_a.sort
268
+ return sorted.first.to_f if sorted.length == 1
269
+
270
+ rank = (p / 100.0) * (sorted.length - 1)
271
+ lower = rank.floor
272
+ upper = rank.ceil
273
+ return sorted[lower].to_f if lower == upper
274
+
275
+ sorted[lower] + ((sorted[upper] - sorted[lower]) * (rank - lower))
276
+ end
277
+
278
+ # Return a random element or array of random elements
279
+ #
280
+ # @param n [Integer, nil] number of elements (nil for single element)
281
+ # @return [Object, Array, nil]
282
+ def sample(n = nil)
283
+ arr = to_a
284
+ return nil if arr.empty? && n.nil?
285
+ return [] if arr.empty? && n
286
+
287
+ n.nil? ? arr.sample : arr.sample(n)
288
+ end
289
+
290
+ # Sliding window averages over elements oldest to newest
291
+ #
292
+ # @param window [Integer] window size
293
+ # @return [Array<Float>]
294
+ def moving_average(window:)
295
+ raise Error, 'buffer is empty' if empty?
296
+ raise Error, 'window must be a positive integer' unless window.is_a?(Integer) && window.positive?
297
+ raise Error, 'window exceeds buffer size' if window > @count
298
+
299
+ arr = to_a
300
+ arr.each { |v| raise Error, 'all elements must be numeric' unless v.is_a?(Numeric) }
301
+
302
+ (0..(arr.length - window)).map do |i|
303
+ arr[i, window].sum.to_f / window
304
+ end
305
+ end
306
+
307
+ # Exponential moving average
308
+ #
309
+ # @param alpha [Float] smoothing factor (0 < alpha <= 1)
310
+ # @return [Float]
311
+ def ema(alpha:)
312
+ raise Error, 'buffer is empty' if empty?
313
+ raise Error, 'alpha must be between 0 (exclusive) and 1 (inclusive)' unless alpha.is_a?(Numeric) && alpha.positive? && alpha <= 1
314
+
315
+ arr = to_a
316
+ arr.each { |v| raise Error, 'all elements must be numeric' unless v.is_a?(Numeric) }
317
+
318
+ result = arr.first.to_f
319
+ arr.drop(1).each do |v|
320
+ result = (alpha * v) + ((1 - alpha) * result)
321
+ end
322
+ result
323
+ end
324
+
247
325
  # Human-readable string representation
248
326
  #
249
327
  # @return [String]
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: philiprehberger-ring_buffer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Philip Rehberger
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-04-14 00:00:00.000000000 Z
11
+ date: 2026-04-16 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Fixed-capacity ring buffer that overwrites oldest entries on overflow,
14
14
  with index access, push/pop/shift mutation, oldest/newest peek, built-in statistics