philiprehberger-ring_buffer 0.5.0 → 0.7.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: 14989919be86bb2eda368bc258ce97204162a5435b48c2d1187f6890934ed5b8
4
- data.tar.gz: 74276b96e9b504278022d5c64c089048eb48fa08e629a441c1064e71947dd95d
3
+ metadata.gz: c341e6a009acc45d1b8ca3ca03851ae4d1f3b713396b23f7be323e53908579d2
4
+ data.tar.gz: f53265859bebd7feec3f5f664f619ac82fb34f047019bb9316855be31c22cda1
5
5
  SHA512:
6
- metadata.gz: 218f50092f5d9f13b4c22e627a4773c06053bc8fd417b5f98a5728bf44aab035ea7253355b695bf76bb0323c161d7c4e8201bf8ccca411d95467ef6fac71d46a
7
- data.tar.gz: 115237b8ffc4260f1bdab21d4529c0e9dc0ff996de8549904c3bdf4bd653a61fecd72e874c0286973973a6fe1a3f0d37222ece924c74b1836444d08e38e25573
6
+ metadata.gz: 710ec989953bac0172e8358fe9eefa1ecf62ab0950522770c34bf7df2f8f17590c1ff5440ee5254897d8b3172bc697a54d168764be622bd6f280af2459600255
7
+ data.tar.gz: 84b6227b2fdabb56f9b3aae4e595fdda1db5906f145c35f7c7b994d163bd8254db49b8549a7197242d2135fac0fc9ecc9da7f43e715d8f9315fe9f66d1871b78
data/CHANGELOG.md CHANGED
@@ -7,6 +7,18 @@ and this gem adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.7.0] - 2026-04-21
11
+
12
+ ### Added
13
+ - `RingBuffer#range` — spread between min and max
14
+ - `RingBuffer#count_by` — bucketed counts by block return value
15
+
16
+ ## [0.6.0] - 2026-04-15
17
+
18
+ ### Added
19
+ - `#moving_average(window:)` for sliding window averages over buffer elements
20
+ - `#ema(alpha:)` for exponential moving average calculation
21
+
10
22
  ## [0.5.0] - 2026-04-14
11
23
 
12
24
  ### Added
@@ -85,6 +97,8 @@ and this gem adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
85
97
  - Last-n element retrieval
86
98
  - Enumerable support
87
99
 
100
+ [0.7.0]: https://github.com/philiprehberger/rb-ring-buffer/releases/tag/v0.7.0
101
+ [0.6.0]: https://github.com/philiprehberger/rb-ring-buffer/releases/tag/v0.6.0
88
102
  [0.5.0]: https://github.com/philiprehberger/rb-ring-buffer/releases/tag/v0.5.0
89
103
  [0.4.0]: https://github.com/philiprehberger/rb-ring-buffer/releases/tag/v0.4.0
90
104
  [0.3.0]: https://github.com/philiprehberger/rb-ring-buffer/releases/tag/v0.3.0
data/README.md CHANGED
@@ -137,6 +137,26 @@ buf.percentile(75) # => 75.25
137
137
  buf.percentile(90) # => 90.1
138
138
  ```
139
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
+ ### Descriptive Helpers
151
+
152
+ ```ruby
153
+ buf = Philiprehberger::RingBuffer.new(10)
154
+ [1, 2, 3, 4, 5, 6].each { |v| buf.push(v) }
155
+
156
+ buf.range # => 5 (max - min)
157
+ buf.count_by(&:even?) # => { false => 3, true => 3 }
158
+ ```
159
+
140
160
  ### Random Sampling
141
161
 
142
162
  ```ruby
@@ -186,6 +206,10 @@ buf.select(&:odd?) # => [1, 3]
186
206
  | `#variance` | Population variance |
187
207
  | `#stddev` | Population standard deviation |
188
208
  | `#median` | Median value |
209
+ | `#moving_average(window:)` | Sliding window averages (oldest to newest) |
210
+ | `#ema(alpha:)` | Exponential moving average (single float) |
211
+ | `#range` | Spread between min and max numeric elements |
212
+ | `#count_by(&block)` | Bucketed counts by block return value |
189
213
 
190
214
  ## Development
191
215
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Philiprehberger
4
4
  class RingBuffer
5
- VERSION = '0.5.0'
5
+ VERSION = '0.7.0'
6
6
  end
7
7
  end
@@ -287,6 +287,41 @@ module Philiprehberger
287
287
  n.nil? ? arr.sample : arr.sample(n)
288
288
  end
289
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
+
290
325
  # Human-readable string representation
291
326
  #
292
327
  # @return [String]
@@ -300,5 +335,31 @@ module Philiprehberger
300
335
  def each(&)
301
336
  to_a.each(&)
302
337
  end
338
+
339
+ # Spread between the minimum and maximum numeric elements
340
+ #
341
+ # @return [Numeric] the difference between max and min
342
+ # @raise [Error] if the buffer is empty
343
+ def range
344
+ raise Error, 'buffer is empty' if empty?
345
+
346
+ max - min
347
+ end
348
+
349
+ # Bucket elements by the block's return value and count each bucket
350
+ #
351
+ # Iterates oldest-to-newest via {#each}. An empty buffer returns `{}`.
352
+ #
353
+ # @yield [element] block whose return value is used as the bucket key
354
+ # @yieldparam element [Object] each element in oldest-to-newest order
355
+ # @return [Hash{Object => Integer}] bucket key to count mapping
356
+ # @return [Enumerator] if no block is given
357
+ def count_by(&block)
358
+ return enum_for(:count_by) unless block
359
+
360
+ result = Hash.new(0)
361
+ each { |element| result[block.call(element)] += 1 }
362
+ result
363
+ end
303
364
  end
304
365
  end
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.5.0
4
+ version: 0.7.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-21 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