Skip to content

Commit a6b64e7

Browse files
committed
Add Circular Buffer solution
1 parent d0f5fe7 commit a6b64e7

File tree

5 files changed

+264
-0
lines changed

5 files changed

+264
-0
lines changed

circular-buffer/.gitignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Generated by Cargo
2+
# will have compiled files and executables
3+
/target/
4+
**/*.rs.bk
5+
6+
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
7+
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
8+
Cargo.lock

circular-buffer/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[package]
2+
name = "circular-buffer"
3+
version = "0.0.0"

circular-buffer/README.md

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Circular Buffer
2+
3+
A circular buffer, cyclic buffer or ring buffer is a data structure that
4+
uses a single, fixed-size buffer as if it were connected end-to-end.
5+
6+
A circular buffer first starts empty and of some predefined length. For
7+
example, this is a 7-element buffer:
8+
9+
[ ][ ][ ][ ][ ][ ][ ]
10+
11+
Assume that a 1 is written into the middle of the buffer (exact starting
12+
location does not matter in a circular buffer):
13+
14+
[ ][ ][ ][1][ ][ ][ ]
15+
16+
Then assume that two more elements are added — 2 & 3 — which get
17+
appended after the 1:
18+
19+
[ ][ ][ ][1][2][3][ ]
20+
21+
If two elements are then removed from the buffer, the oldest values
22+
inside the buffer are removed. The two elements removed, in this case,
23+
are 1 & 2, leaving the buffer with just a 3:
24+
25+
[ ][ ][ ][ ][ ][3][ ]
26+
27+
If the buffer has 7 elements then it is completely full:
28+
29+
[6][7][8][9][3][4][5]
30+
31+
When the buffer is full an error will be raised, alerting the client
32+
that further writes are blocked until a slot becomes free.
33+
34+
When the buffer is full, the client can opt to overwrite the oldest
35+
data with a forced write. In this case, two more elements — A & B —
36+
are added and they overwrite the 3 & 4:
37+
38+
[6][7][8][9][A][B][5]
39+
40+
3 & 4 have been replaced by A & B making 5 now the oldest data in the
41+
buffer. Finally, if two elements are removed then what would be
42+
returned is 5 & 6 yielding the buffer:
43+
44+
[ ][7][8][9][A][B][ ]
45+
46+
Because there is space available, if the client again uses overwrite
47+
to store C & D then the space where 5 & 6 were stored previously will
48+
be used not the location of 7 & 8. 7 is still the oldest element and
49+
the buffer is once again full.
50+
51+
[D][7][8][9][A][B][C]
52+
53+
## Rust Installation
54+
55+
Refer to the [exercism help page][help-page] for Rust installation and learning
56+
resources.
57+
58+
## Writing the Code
59+
60+
Execute the tests with:
61+
62+
```bash
63+
$ cargo test
64+
```
65+
66+
All but the first test have been ignored. After you get the first test to
67+
pass, open the tests source file which is located in the `tests` directory
68+
and remove the `#[ignore]` flag from the next test and get the tests to pass
69+
again. Each separate test is a function with `#[test]` flag above it.
70+
Continue, until you pass every test.
71+
72+
If you wish to run all tests without editing the tests source file, use:
73+
74+
```bash
75+
$ cargo test -- --ignored
76+
```
77+
78+
To run a specific test, for example `some_test`, you can use:
79+
80+
```bash
81+
$ cargo test some_test
82+
```
83+
84+
If the specific test is ignored use:
85+
86+
```bash
87+
$ cargo test some_test -- --ignored
88+
```
89+
90+
To learn more about Rust tests refer to the [online test documentation][rust-tests]
91+
92+
Make sure to read the [Modules](https://doc.rust-lang.org/book/second-edition/ch07-00-modules.html) chapter if you
93+
haven't already, it will help you with organizing your files.
94+
95+
## Feedback, Issues, Pull Requests
96+
97+
The [exercism/rust](https://github.com/exercism/rust) repository on GitHub is the home for all of the Rust exercises. If you have feedback about an exercise, or want to help implement new exercises, head over there and create an issue. Members of the rust track team are happy to help!
98+
99+
If you want to know more about Exercism, take a look at the [contribution guide](https://github.com/exercism/docs/blob/master/contributing-to-language-tracks/README.md).
100+
101+
[help-page]: http://exercism.io/languages/rust
102+
[modules]: https://doc.rust-lang.org/book/second-edition/ch07-00-modules.html
103+
[cargo]: https://doc.rust-lang.org/book/second-edition/ch14-00-more-about-cargo.html
104+
[rust-tests]: https://doc.rust-lang.org/book/second-edition/ch11-02-running-tests.html
105+
106+
## Source
107+
108+
Wikipedia [http://en.wikipedia.org/wiki/Circular_buffer](http://en.wikipedia.org/wiki/Circular_buffer)
109+
110+
## Submitting Incomplete Solutions
111+
It's possible to submit an incomplete solution so you can see how others have completed the exercise.

circular-buffer/src/lib.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
use std::collections::VecDeque;
2+
3+
#[derive(Debug, PartialEq)]
4+
pub enum Error {
5+
EmptyBuffer,
6+
FullBuffer,
7+
}
8+
9+
pub struct CircularBuffer<T> {
10+
size: usize,
11+
buffer: VecDeque<T>,
12+
}
13+
14+
impl<T> CircularBuffer<T> {
15+
pub fn new(size: usize) -> Self {
16+
CircularBuffer {
17+
size,
18+
buffer: VecDeque::with_capacity(size),
19+
}
20+
}
21+
fn is_buffer_full(&self) -> bool {
22+
self.buffer.len() >= self.size
23+
}
24+
pub fn read(&mut self) -> Result<T, Error> {
25+
self.buffer.pop_front().ok_or(Error::EmptyBuffer)
26+
}
27+
pub fn write(&mut self, value: T) -> Result<(), Error> {
28+
if self.is_buffer_full() {
29+
return Err(Error::FullBuffer);
30+
}
31+
self.buffer.push_back(value);
32+
Ok(())
33+
}
34+
pub fn clear(&mut self) {
35+
self.buffer.clear();
36+
}
37+
pub fn overwrite(&mut self, value: T) {
38+
if self.is_buffer_full() {
39+
self.buffer.pop_front();
40+
}
41+
self.buffer.push_back(value);
42+
}
43+
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
extern crate circular_buffer;
2+
use circular_buffer::{CircularBuffer, Error};
3+
4+
#[test]
5+
fn error_on_read_empty_buffer() {
6+
let mut buffer = CircularBuffer::<char>::new(1);
7+
assert_eq!(Err(Error::EmptyBuffer), buffer.read());
8+
}
9+
10+
#[test]
11+
fn write_and_read_back_item() {
12+
let mut buffer = CircularBuffer::new(1);
13+
assert!(buffer.write('1').is_ok());
14+
assert_eq!(Ok('1'), buffer.read());
15+
assert_eq!(Err(Error::EmptyBuffer), buffer.read());
16+
}
17+
18+
#[test]
19+
fn write_and_read_back_multiple_items() {
20+
let mut buffer = CircularBuffer::new(2);
21+
assert!(buffer.write('1').is_ok());
22+
assert!(buffer.write('2').is_ok());
23+
assert_eq!(Ok('1'), buffer.read());
24+
assert_eq!(Ok('2'), buffer.read());
25+
assert_eq!(Err(Error::EmptyBuffer), buffer.read());
26+
}
27+
28+
#[test]
29+
fn alternate_write_and_read() {
30+
let mut buffer = CircularBuffer::new(2);
31+
assert!(buffer.write('1').is_ok());
32+
assert_eq!(Ok('1'), buffer.read());
33+
assert!(buffer.write('2').is_ok());
34+
assert_eq!(Ok('2'), buffer.read());
35+
}
36+
37+
#[test]
38+
fn clear_buffer() {
39+
let mut buffer = CircularBuffer::new(3);
40+
assert!(buffer.write('1').is_ok());
41+
assert!(buffer.write('2').is_ok());
42+
assert!(buffer.write('3').is_ok());
43+
buffer.clear();
44+
assert_eq!(Err(Error::EmptyBuffer), buffer.read());
45+
assert!(buffer.write('1').is_ok());
46+
assert!(buffer.write('2').is_ok());
47+
assert_eq!(Ok('1'), buffer.read());
48+
assert!(buffer.write('3').is_ok());
49+
assert_eq!(Ok('2'), buffer.read());
50+
}
51+
52+
#[test]
53+
fn full_buffer_error() {
54+
let mut buffer = CircularBuffer::new(2);
55+
assert!(buffer.write('1').is_ok());
56+
assert!(buffer.write('2').is_ok());
57+
assert_eq!(Err(Error::FullBuffer), buffer.write('3'));
58+
}
59+
60+
#[test]
61+
fn overwrite_item_in_non_full_buffer() {
62+
let mut buffer = CircularBuffer::new(2);
63+
assert!(buffer.write('1').is_ok());
64+
buffer.overwrite('2');
65+
assert_eq!(Ok('1'), buffer.read());
66+
assert_eq!(Ok('2'), buffer.read());
67+
assert_eq!(Err(Error::EmptyBuffer), buffer.read());
68+
}
69+
70+
#[test]
71+
fn overwrite_item_in_full_buffer() {
72+
let mut buffer = CircularBuffer::new(2);
73+
assert!(buffer.write('1').is_ok());
74+
assert!(buffer.write('2').is_ok());
75+
buffer.overwrite('A');
76+
assert_eq!(Ok('2'), buffer.read());
77+
assert_eq!(Ok('A'), buffer.read());
78+
}
79+
80+
#[test]
81+
fn integer_buffer() {
82+
let mut buffer = CircularBuffer::new(2);
83+
assert!(buffer.write(1).is_ok());
84+
assert!(buffer.write(2).is_ok());
85+
assert_eq!(Ok(1), buffer.read());
86+
assert!(buffer.write(-1).is_ok());
87+
assert_eq!(Ok(2), buffer.read());
88+
assert_eq!(Ok(-1), buffer.read());
89+
assert_eq!(Err(Error::EmptyBuffer), buffer.read());
90+
}
91+
92+
#[test]
93+
fn string_buffer() {
94+
let mut buffer = CircularBuffer::new(2);
95+
buffer.write("".to_string()).unwrap();
96+
buffer.write("Testing".to_string()).unwrap();
97+
assert_eq!(0, buffer.read().unwrap().len());
98+
assert_eq!(Ok("Testing".to_string()), buffer.read());
99+
}

0 commit comments

Comments
 (0)