Class: Philiprehberger::RingBuffer

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/philiprehberger/ring_buffer.rb,
lib/philiprehberger/ring_buffer/version.rb

Defined Under Namespace

Classes: Error

Constant Summary collapse

VERSION =
'0.5.0'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(capacity) ⇒ RingBuffer

Create a new ring buffer with fixed capacity

Parameters:

  • capacity (Integer)

    maximum number of elements

Raises:



16
17
18
19
20
21
22
23
# File 'lib/philiprehberger/ring_buffer.rb', line 16

def initialize(capacity)
  raise Error, 'capacity must be a positive integer' unless capacity.is_a?(Integer) && capacity.positive?

  @capacity = capacity
  @buffer = Array.new(capacity)
  @head = 0
  @count = 0
end

Instance Attribute Details

#capacityObject (readonly)

Returns the value of attribute capacity.



11
12
13
# File 'lib/philiprehberger/ring_buffer.rb', line 11

def capacity
  @capacity
end

Instance Method Details

#[](index) ⇒ Object?

Access element by index (0 = oldest, -1 = newest)

Parameters:

  • index (Integer)

Returns:

  • (Object, nil)


161
162
163
164
165
166
167
# File 'lib/philiprehberger/ring_buffer.rb', line 161

def [](index)
  arr = to_a
  return nil if arr.empty?
  return nil if index >= arr.length || index < -arr.length

  arr[index]
end

#averageFloat

Calculate average of numeric elements

Returns:

  • (Float)

Raises:



115
116
117
118
119
# File 'lib/philiprehberger/ring_buffer.rb', line 115

def average
  raise Error, 'buffer is empty' if empty?

  sum.to_f / @count
end

#clearself

Remove all elements and reset internal state

Returns:

  • (self)


183
184
185
186
187
188
# File 'lib/philiprehberger/ring_buffer.rb', line 183

def clear
  @buffer = Array.new(@capacity)
  @head = 0
  @count = 0
  self
end

#concat(*values) ⇒ self

Push multiple values at once

Parameters:

  • values (Array<Object>)

    values to push

Returns:

  • (self)


251
252
253
254
# File 'lib/philiprehberger/ring_buffer.rb', line 251

def concat(*values)
  values.each { |v| push(v) }
  self
end

#each {|element| ... } ⇒ Object

Iterate over elements (oldest first)

Yields:

  • (element)


300
301
302
# File 'lib/philiprehberger/ring_buffer.rb', line 300

def each(&)
  to_a.each(&)
end

#empty?Boolean

Check if the buffer is empty

Returns:

  • (Boolean)


108
109
110
# File 'lib/philiprehberger/ring_buffer.rb', line 108

def empty?
  @count.zero?
end

#first(n = 1) ⇒ Object, Array

Return the oldest n elements

Parameters:

  • n (Integer) (defaults to: 1)

    number of elements (default 1)

Returns:

  • (Object, Array)


173
174
175
176
177
178
# File 'lib/philiprehberger/ring_buffer.rb', line 173

def first(n = 1)
  arr = to_a
  return arr.first if n == 1

  arr.first(n)
end

#full?Boolean

Check if the buffer is full

Returns:

  • (Boolean)


101
102
103
# File 'lib/philiprehberger/ring_buffer.rb', line 101

def full?
  @count == @capacity
end

#inspectString

Human-readable string representation

Returns:

  • (String)


293
294
295
# File 'lib/philiprehberger/ring_buffer.rb', line 293

def inspect
  "#<#{self.class} capacity=#{@capacity} size=#{@count} elements=#{to_a.inspect}>"
end

#last(n = 1) ⇒ Array

Return the last n elements (most recent)

Parameters:

  • n (Integer) (defaults to: 1)

    number of elements

Returns:

  • (Array)


152
153
154
155
# File 'lib/philiprehberger/ring_buffer.rb', line 152

def last(n = 1)
  arr = to_a
  arr.last(n)
end

#maxObject

Find maximum element

Returns:

  • (Object)

Raises:



142
143
144
145
146
# File 'lib/philiprehberger/ring_buffer.rb', line 142

def max
  raise Error, 'buffer is empty' if empty?

  to_a.max
end

#medianFloat?

Median value of numeric elements

Returns:

  • (Float, nil)


211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/philiprehberger/ring_buffer.rb', line 211

def median
  return nil if empty?

  sorted = to_a.sort
  mid = sorted.length / 2

  if sorted.length.odd?
    sorted[mid].to_f
  else
    (sorted[mid - 1] + sorted[mid]) / 2.0
  end
end

#minObject

Find minimum element

Returns:

  • (Object)

Raises:



133
134
135
136
137
# File 'lib/philiprehberger/ring_buffer.rb', line 133

def min
  raise Error, 'buffer is empty' if empty?

  to_a.min
end

#newestObject?

Return the newest element without removing it

Returns:

  • (Object, nil)


85
86
87
88
89
# File 'lib/philiprehberger/ring_buffer.rb', line 85

def newest
  return nil if empty?

  @buffer[(@head - 1) % @capacity]
end

#oldestObject?

Return the oldest element without removing it

Returns:

  • (Object, nil)


76
77
78
79
80
# File 'lib/philiprehberger/ring_buffer.rb', line 76

def oldest
  return nil if empty?

  @buffer[(@head - @count) % @capacity]
end

#percentile(p) ⇒ Float?

Calculate the p-th percentile of numeric elements

Uses linear interpolation between nearest ranks.

Parameters:

  • p (Numeric)

    percentile (0-100)

Returns:

  • (Float, nil)

    the percentile value, or nil if empty

Raises:

  • (Error)

    if p is outside 0-100



263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/philiprehberger/ring_buffer.rb', line 263

def percentile(p)
  raise Error, 'percentile must be between 0 and 100' unless p.is_a?(Numeric) && p >= 0 && p <= 100
  return nil if empty?

  sorted = to_a.sort
  return sorted.first.to_f if sorted.length == 1

  rank = (p / 100.0) * (sorted.length - 1)
  lower = rank.floor
  upper = rank.ceil
  return sorted[lower].to_f if lower == upper

  sorted[lower] + ((sorted[upper] - sorted[lower]) * (rank - lower))
end

#popObject?

Remove and return the newest element

Returns:

  • (Object, nil)


62
63
64
65
66
67
68
69
70
71
# File 'lib/philiprehberger/ring_buffer.rb', line 62

def pop
  return nil if empty?

  newest_idx = (@head - 1) % @capacity
  value = @buffer[newest_idx]
  @buffer[newest_idx] = nil
  @head = newest_idx
  @count -= 1
  value
end

#push(value) ⇒ self

Push a value into the buffer, overwriting oldest if full

Parameters:

  • value (Object)

Returns:

  • (self)


29
30
31
32
33
34
# File 'lib/philiprehberger/ring_buffer.rb', line 29

def push(value)
  @buffer[@head] = value
  @head = (@head + 1) % @capacity
  @count += 1 if @count < @capacity
  self
end

#resize(new_capacity) ⇒ self

Change the buffer capacity, preserving elements

If the new capacity is smaller than the current element count, the oldest elements are discarded and only the most recent new_capacity elements are kept.

Parameters:

  • new_capacity (Integer)

    the new maximum number of elements

Returns:

  • (self)

Raises:



232
233
234
235
236
237
238
239
240
241
242
243
244
245
# File 'lib/philiprehberger/ring_buffer.rb', line 232

def resize(new_capacity)
  raise Error, 'capacity must be a positive integer' unless new_capacity.is_a?(Integer) && new_capacity.positive?
  return self if new_capacity == @capacity

  elements = to_a
  elements = elements.last(new_capacity) if elements.length > new_capacity

  @capacity = new_capacity
  @buffer = Array.new(new_capacity)
  @head = 0
  @count = 0
  elements.each { |e| push(e) }
  self
end

#sample(n = nil) ⇒ Object, ...

Return a random element or array of random elements

Parameters:

  • n (Integer, nil) (defaults to: nil)

    number of elements (nil for single element)

Returns:

  • (Object, Array, nil)


282
283
284
285
286
287
288
# File 'lib/philiprehberger/ring_buffer.rb', line 282

def sample(n = nil)
  arr = to_a
  return nil if arr.empty? && n.nil?
  return [] if arr.empty? && n

  n.nil? ? arr.sample : arr.sample(n)
end

#shiftObject?

Remove and return the oldest element

Returns:

  • (Object, nil)


49
50
51
52
53
54
55
56
57
# File 'lib/philiprehberger/ring_buffer.rb', line 49

def shift
  return nil if empty?

  start = (@head - @count) % @capacity
  value = @buffer[start]
  @buffer[start] = nil
  @count -= 1
  value
end

#sizeInteger

Number of elements currently in the buffer

Returns:

  • (Integer)


94
95
96
# File 'lib/philiprehberger/ring_buffer.rb', line 94

def size
  @count
end

#stddevFloat

Population standard deviation of numeric elements

Returns:

  • (Float)


204
205
206
# File 'lib/philiprehberger/ring_buffer.rb', line 204

def stddev
  Math.sqrt(variance)
end

#sumNumeric

Calculate sum of numeric elements

Returns:

  • (Numeric)

Raises:



124
125
126
127
128
# File 'lib/philiprehberger/ring_buffer.rb', line 124

def sum
  raise Error, 'buffer is empty' if empty?

  to_a.sum
end

#to_aArray

Convert buffer contents to an array (oldest first)

Returns:

  • (Array)


39
40
41
42
43
44
# File 'lib/philiprehberger/ring_buffer.rb', line 39

def to_a
  return [] if @count.zero?

  start = (@head - @count) % @capacity
  Array.new(@count) { |i| @buffer[(start + i) % @capacity] }
end

#varianceFloat

Population variance of numeric elements

Returns:

  • (Float)


193
194
195
196
197
198
199
# File 'lib/philiprehberger/ring_buffer.rb', line 193

def variance
  return 0.0 if @count <= 1

  avg = average
  arr = to_a
  arr.sum { |v| (v - avg)**2 } / arr.length.to_f
end