fixed_size_buffer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []