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 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
@@ -0,0 +1,4 @@
1
+ Release v0.1.0
2
+ ====================
3
+
4
+ Initial public release
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
+ [![Gem Version](https://badge.fury.io/rb/fixed_size_buffer.svg)](https://badge.fury.io/rb/fixed_size_buffer)
5
+ [![CI](https://github.com/justinhoward/fixed_size_buffer/workflows/CI/badge.svg)](https://github.com/justinhoward/fixed_size_buffer/actions?query=workflow%3ACI+branch%3Amaster)
6
+ [![Code Quality](https://app.codacy.com/project/badge/Grade/e647db61b5064a6e97fc20ffe7f0430e)](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
+ [![Code Coverage](https://app.codacy.com/project/badge/Grade/e647db61b5064a6e97fc20ffe7f0430e)](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
+ [![Inline docs](http://inch-ci.org/github/justinhoward/fixed_size_buffer.svg?branch=master)](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
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class FixedSizeBuffer
4
+ # The current FixedSizeBuffer gem version
5
+ VERSION = '0.1.0'
6
+
7
+ def self.version
8
+ Gem::Version.new(VERSION)
9
+ end
10
+ 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: []