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 +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +45 -0
- data/lib/philiprehberger/ring_buffer/version.rb +1 -1
- data/lib/philiprehberger/ring_buffer.rb +78 -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: 296d9b4ccfe2ca3cacba6bf34911e142381b0f8e40d2cdf0ed8f23432ff957a3
|
|
4
|
+
data.tar.gz: cfe72d59c6b58f6d0be55488a17d1760c3914ecad2ed5a91bd2dc903c8d8c8a1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
|
@@ -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
|
+
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-
|
|
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
|