fixed_size_buffer 0.1.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 +7 -0
- data/CHANGELOG.md +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +96 -0
- data/lib/fixed_size_buffer.rb +160 -0
- data/lib/fixed_size_buffer/version.rb +10 -0
- metadata +91 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1754b5a2e7a437b5fc4b705b420d7ba81a03bd0265aba64001b850c3c7b5d95c
|
4
|
+
data.tar.gz: 5dbc25d63dc6fe6f1485790080010339b1f7381e19ee4f518dcbf2c033be1dad
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ae54776240a4611208a9af5c535eb050bf5cfae228f1acf86d298b7230dd4572717fa944d7b5ef22febc65ed8d837966e4eb3756d351cb9fe3b1fc373b6b07ae
|
7
|
+
data.tar.gz: f52a8abf718310388808f5f629b4dcacb622f403f8088ee37aebb8a9e2c6468290909d5cb8fef0f58deaac7e2fd99e35543d00c58f7ca32059da5fff8291a583
|
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2021 Justin Howard
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
FixedSizeBuffer
|
2
|
+
==================
|
3
|
+
|
4
|
+
[](https://badge.fury.io/rb/fixed_size_buffer)
|
5
|
+
[](https://github.com/justinhoward/fixed_size_buffer/actions?query=workflow%3ACI+branch%3Amaster)
|
6
|
+
[](https://www.codacy.com/gh/justinhoward/fixed_size_buffer/dashboard?utm_source=github.com&utm_medium=referral&utm_content=justinhoward/fixed_size_buffer&utm_campaign=Badge_Grade)
|
7
|
+
[](https://www.codacy.com/gh/justinhoward/fixed_size_buffer/dashboard?utm_source=github.com&utm_medium=referral&utm_content=justinhoward/fixed_size_buffer&utm_campaign=Badge_Grade)
|
8
|
+
[](http://inch-ci.org/github/justinhoward/fixed_size_buffer)
|
9
|
+
|
10
|
+
A ring buffer with a fixed capacity
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
buffer = FixedSizeBuffer.new(3)
|
14
|
+
buffer.write(1)
|
15
|
+
buffer.write(2)
|
16
|
+
buffer.to_a # [1, 2]
|
17
|
+
buffer.read # => 1
|
18
|
+
buffer.read # => 2
|
19
|
+
```
|
20
|
+
|
21
|
+
What is a Ring Buffer?
|
22
|
+
------------------------
|
23
|
+
|
24
|
+
A ring buffer is a continuously writable and continuously readable data
|
25
|
+
structure that is contained in a fixed amount of memory. It can be
|
26
|
+
conceptualized as writing values around a circular array. As we write, we move
|
27
|
+
clockwise around the circle, and the reader trails around in the same direction.
|
28
|
+
|
29
|
+
The advantages of a ring buffer as opposed to pushing and shifting an array is
|
30
|
+
that a ring buffer only can be heap allocated once, and since it's a fixed size,
|
31
|
+
it never needs to be reallocated.
|
32
|
+
|
33
|
+
Installation
|
34
|
+
---------------
|
35
|
+
|
36
|
+
Add it to your `Gemfile`:
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
gem 'fixed_size_buffer'
|
40
|
+
```
|
41
|
+
|
42
|
+
Or install it manually:
|
43
|
+
|
44
|
+
```sh
|
45
|
+
gem install fixed_size_buffer
|
46
|
+
```
|
47
|
+
|
48
|
+
API Documentation
|
49
|
+
--------------
|
50
|
+
|
51
|
+
API docs can be read [on rubydoc.info][api docs], inline in the source code, or
|
52
|
+
you can generate them yourself with Ruby `yard`:
|
53
|
+
|
54
|
+
```sh
|
55
|
+
bin/yardoc
|
56
|
+
```
|
57
|
+
|
58
|
+
Then open `doc/index.html` in your browser.
|
59
|
+
|
60
|
+
Usage
|
61
|
+
-----------
|
62
|
+
|
63
|
+
Create a new ring buffer with a given capacity
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
buffer = FixedSizeBuffer.new(100)
|
67
|
+
```
|
68
|
+
|
69
|
+
Then write and read from the buffer
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
buffer = FixedSizeBuffer.new(2)
|
73
|
+
buffer.empty? # => true
|
74
|
+
buffer.size # => 0
|
75
|
+
buffer.write('hello')
|
76
|
+
buffer.write('world')
|
77
|
+
buffer.size # => 2
|
78
|
+
buffer.full? # => true
|
79
|
+
buffer.to_a # => ['hello', 'world']
|
80
|
+
buffer.peek # => 'hello'
|
81
|
+
buffer.read # => 'hello'
|
82
|
+
buffer.read # => 'world'
|
83
|
+
buffer.read # => nil
|
84
|
+
```
|
85
|
+
|
86
|
+
Once the buffer fills up, it will overwrite the oldest values.
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
buffer = FixedSizeBuffer.new(2)
|
90
|
+
buffer.write('hello')
|
91
|
+
buffer.write('world')
|
92
|
+
buffer.write('again')
|
93
|
+
buffer.read # => 'world'
|
94
|
+
```
|
95
|
+
|
96
|
+
[api docs]: https://www.rubydoc.info/github/justinhoward/fixed_size_buffer/master
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fixed_size_buffer/version'
|
4
|
+
|
5
|
+
# A ring buffer with a fixed capacity
|
6
|
+
#
|
7
|
+
# Principal of Operation
|
8
|
+
# ========================
|
9
|
+
# A ring buffer is a continuously writable and continuously readable
|
10
|
+
# data structure that is contained in a fixed amount of memory. It can be
|
11
|
+
# conceptualized as writing values around a circular array. As we write,
|
12
|
+
# we move clockwise around the circle, and reading trails around in the
|
13
|
+
# same direction.
|
14
|
+
#
|
15
|
+
# If the reader catches up to the writer, the buffer is empty. If the writer
|
16
|
+
# wraps around and catches up to the reader, the buffer is full. In our case,
|
17
|
+
# we allow the writer to continue writing to a full buffer, which causes
|
18
|
+
# the oldest values to be dropped.
|
19
|
+
#
|
20
|
+
# Implementation
|
21
|
+
# =======================
|
22
|
+
# This ring buffer is implemented as a fixed-size array. We also maintain
|
23
|
+
# a read index and a write index. These are the positions where the next read
|
24
|
+
# or write will occur. We increase the write pointer by one each time we write
|
25
|
+
# to the buffer. We also increase the read pointer by one every time we read.
|
26
|
+
#
|
27
|
+
# The buffer is considered empty if the read and write pointers are in the
|
28
|
+
# same position. In this state, reading will not move the read pointer, and
|
29
|
+
# `nil` will be returned.
|
30
|
+
#
|
31
|
+
# An empty buffer of capacity 3.
|
32
|
+
# ```text
|
33
|
+
# rw
|
34
|
+
# | | | | |
|
35
|
+
# ```
|
36
|
+
#
|
37
|
+
# The buffer is considered full if the write pointer is in the position
|
38
|
+
# immediately before the read pointer. In this state, writing will overwrite
|
39
|
+
# the next position, which will be the oldest value in the buffer. Writing
|
40
|
+
# in this state will also move the read pointer forward so that the next
|
41
|
+
# read will continue from the next-oldest value.
|
42
|
+
#
|
43
|
+
# A full buffer of capacity 3. Note that the 2 here is already considered
|
44
|
+
# overwritten.
|
45
|
+
# ```text
|
46
|
+
# w r
|
47
|
+
# | 5 | 2 | 3 | 4 |
|
48
|
+
# ```
|
49
|
+
#
|
50
|
+
# Another full buffer with the same values but in a wrapped-around position.
|
51
|
+
# ```text
|
52
|
+
# r w
|
53
|
+
# | 3 | 4 | 5 | 2 |
|
54
|
+
# ```
|
55
|
+
#
|
56
|
+
# Notice that the array for this buffer has one extra position. This is used to
|
57
|
+
# differentiate between an empty buffer (read/write pointers in same position),
|
58
|
+
# and a full buffer (write pointer just before read pointer). The value
|
59
|
+
# in-between the read and write pointers for a full buffer will not be read.
|
60
|
+
class FixedSizeBuffer
|
61
|
+
attr_reader :capacity
|
62
|
+
|
63
|
+
# Create a new empty {FixedSizeBuffer}
|
64
|
+
#
|
65
|
+
# Start state
|
66
|
+
# rw
|
67
|
+
# | | | | |
|
68
|
+
#
|
69
|
+
# @param capacity [Integer] The number of items this buffer can hold
|
70
|
+
def initialize(capacity)
|
71
|
+
@capacity = capacity
|
72
|
+
@buffer = Array.new(capacity + 1)
|
73
|
+
@read = 0
|
74
|
+
@write = 0
|
75
|
+
end
|
76
|
+
|
77
|
+
# Read one value from the buffer
|
78
|
+
#
|
79
|
+
# The value will be "consumed", meaning it will no longer be readable. If the
|
80
|
+
# buffer is empty, this will return nil, so if it matters, check `empty?`
|
81
|
+
# before calling `read`.
|
82
|
+
#
|
83
|
+
# @return [Object, nil] The value that was read, or nil if empty
|
84
|
+
def read
|
85
|
+
return nil if empty?
|
86
|
+
|
87
|
+
value = @buffer[@read]
|
88
|
+
@read = (@read + 1) % @buffer.size
|
89
|
+
value
|
90
|
+
end
|
91
|
+
|
92
|
+
# Read a value from the buffer without consuming it
|
93
|
+
#
|
94
|
+
# Like {#read}, this will return nil when the buffer is empty, so
|
95
|
+
# check {#empty} if you need to distinguish between the two states.
|
96
|
+
#
|
97
|
+
# @return [Object, nil] The value that was read, or nil if empty
|
98
|
+
def peek
|
99
|
+
return nil if empty?
|
100
|
+
|
101
|
+
@buffer[@read]
|
102
|
+
end
|
103
|
+
|
104
|
+
# Write one value to the buffer
|
105
|
+
#
|
106
|
+
# Write a value to thee buffer
|
107
|
+
#
|
108
|
+
# @param value [Object] The value to write
|
109
|
+
# @return [void]
|
110
|
+
def write(value)
|
111
|
+
@buffer[@write] = value
|
112
|
+
|
113
|
+
# If the buffer is full before a write, we drop the oldest value by
|
114
|
+
# moving the read pointer as well
|
115
|
+
@read = (@read + 1) % @buffer.size if full?
|
116
|
+
@write = (@write + 1) % @buffer.size
|
117
|
+
nil
|
118
|
+
end
|
119
|
+
|
120
|
+
# Is the buffer full?
|
121
|
+
#
|
122
|
+
# A full buffer does not prevent writes, but it does mean that the next
|
123
|
+
# write will overwrite an unread value.
|
124
|
+
#
|
125
|
+
# @return [Boolean] True if the buffer is full
|
126
|
+
def full?
|
127
|
+
# When full, the write pointer will be one less than the read pointer
|
128
|
+
# (mod buffer size).
|
129
|
+
(@write + 1) % @buffer.size == @read
|
130
|
+
end
|
131
|
+
|
132
|
+
def empty?
|
133
|
+
# When empty, the read and write pointers will be in the same place.
|
134
|
+
@read == @write
|
135
|
+
end
|
136
|
+
|
137
|
+
# The number of unread items
|
138
|
+
#
|
139
|
+
# @return [Integer] The buffer size
|
140
|
+
def size
|
141
|
+
if @write >= @read
|
142
|
+
@write - @read
|
143
|
+
else
|
144
|
+
@buffer.size - @read + @write
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
# Get an array of all the unread items in the buffer
|
149
|
+
#
|
150
|
+
# Unlike {#read}, this does not consume buffer items
|
151
|
+
#
|
152
|
+
# @return [Array<Object>] An array having length of {#size}
|
153
|
+
def to_a
|
154
|
+
if @write >= @read
|
155
|
+
@buffer[@read...@write]
|
156
|
+
else
|
157
|
+
@buffer[@read...@buffer.size] + @buffer[0...@write]
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fixed_size_buffer
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Justin Howard
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-03-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.4'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.4'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rubocop
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 0.81.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 0.81.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubocop-rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 1.38.1
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.38.1
|
55
|
+
description:
|
56
|
+
email:
|
57
|
+
- jmhoward0@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- CHANGELOG.md
|
63
|
+
- LICENSE.txt
|
64
|
+
- README.md
|
65
|
+
- lib/fixed_size_buffer.rb
|
66
|
+
- lib/fixed_size_buffer/version.rb
|
67
|
+
homepage: https://github.com/justinhoward/fixed_size_buffer
|
68
|
+
licenses:
|
69
|
+
- MIT
|
70
|
+
metadata:
|
71
|
+
changelog_uri: https://github.com/justinhoward/fixed_size_buffer/blob/master/CHANGELOG.md
|
72
|
+
post_install_message:
|
73
|
+
rdoc_options: []
|
74
|
+
require_paths:
|
75
|
+
- lib
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '2.3'
|
81
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
requirements: []
|
87
|
+
rubygems_version: 3.1.2
|
88
|
+
signing_key:
|
89
|
+
specification_version: 4
|
90
|
+
summary: A circular data structure
|
91
|
+
test_files: []
|