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 +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +24 -0
- data/lib/philiprehberger/ring_buffer/version.rb +1 -1
- data/lib/philiprehberger/ring_buffer.rb +61 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c341e6a009acc45d1b8ca3ca03851ae4d1f3b713396b23f7be323e53908579d2
|
|
4
|
+
data.tar.gz: f53265859bebd7feec3f5f664f619ac82fb34f047019bb9316855be31c22cda1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
|
@@ -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.
|
|
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-
|
|
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
|