Skip to content

Commit 1144087

Browse files
authored
Add Circular Buffer Exercise (#624)
1 parent 45a933b commit 1144087

File tree

7 files changed

+482
-0
lines changed

7 files changed

+482
-0
lines changed

config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1171,6 +1171,14 @@
11711171
"practices": [],
11721172
"prerequisites": [],
11731173
"difficulty": 7
1174+
},
1175+
{
1176+
"slug": "circular-buffer",
1177+
"name": "Circular Buffer",
1178+
"uuid": "3f077d57-472a-469a-885d-ebc6aaccd3d7",
1179+
"practices": [],
1180+
"prerequisites": [],
1181+
"difficulty": 6
11741182
}
11751183
]
11761184
},
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Instructions
2+
3+
A circular buffer, cyclic buffer or ring buffer is a data structure that uses a single, fixed-size buffer as if it were connected end-to-end.
4+
5+
A circular buffer first starts empty and of some predefined length.
6+
For example, this is a 7-element buffer:
7+
8+
```text
9+
[ ][ ][ ][ ][ ][ ][ ]
10+
```
11+
12+
Assume that a 1 is written into the middle of the buffer (exact starting location does not matter in a circular buffer):
13+
14+
```text
15+
[ ][ ][ ][1][ ][ ][ ]
16+
```
17+
18+
Then assume that two more elements are added — 2 & 3 — which get appended after the 1:
19+
20+
```text
21+
[ ][ ][ ][1][2][3][ ]
22+
```
23+
24+
If two elements are then removed from the buffer, the oldest values inside the buffer are removed.
25+
The two elements removed, in this case, are 1 & 2, leaving the buffer with just a 3:
26+
27+
```text
28+
[ ][ ][ ][ ][ ][3][ ]
29+
```
30+
31+
If the buffer has 7 elements then it is completely full:
32+
33+
```text
34+
[5][6][7][8][9][3][4]
35+
```
36+
37+
When the buffer is full an error will be raised, alerting the client that further writes are blocked until a slot becomes free.
38+
39+
When the buffer is full, the client can opt to overwrite the oldest data with a forced write.
40+
In this case, two more elements — A & B — are added and they overwrite the 3 & 4:
41+
42+
```text
43+
[5][6][7][8][9][A][B]
44+
```
45+
46+
3 & 4 have been replaced by A & B making 5 now the oldest data in the buffer.
47+
Finally, if two elements are removed then what would be returned is 5 & 6 yielding the buffer:
48+
49+
```text
50+
[ ][ ][7][8][9][A][B]
51+
```
52+
53+
Because there is space available, if the client again uses overwrite to store C & D then the space where 5 & 6 were stored previously will be used not the location of 7 & 8.
54+
7 is still the oldest element and the buffer is once again full.
55+
56+
```text
57+
[C][D][7][8][9][A][B]
58+
```
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"authors": [
3+
"tomasnorre"
4+
],
5+
"files": {
6+
"solution": [
7+
"CircularBuffer.php"
8+
],
9+
"test": [
10+
"CircularBufferTest.php"
11+
],
12+
"example": [
13+
".meta/example.php"
14+
]
15+
},
16+
"blurb": "A data structure that uses a single, fixed-size buffer as if it were connected end-to-end.",
17+
"source": "Wikipedia",
18+
"source_url": "https://en.wikipedia.org/wiki/Circular_buffer"
19+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?php
2+
3+
/*
4+
* By adding type hints and enabling strict type checking, code can become
5+
* easier to read, self-documenting and reduce the number of potential bugs.
6+
* By default, type declarations are non-strict, which means they will attempt
7+
* to change the original type to match the type specified by the
8+
* type-declaration.
9+
*
10+
* In other words, if you pass a string to a function requiring a float,
11+
* it will attempt to convert the string value to a float.
12+
*
13+
* To enable strict mode, a single declare directive must be placed at the top
14+
* of the file.
15+
* This means that the strictness of typing is configured on a per-file basis.
16+
* This directive not only affects the type declarations of parameters, but also
17+
* a function's return type.
18+
*
19+
* For more info review the Concept on strict type checking in the PHP track
20+
* <link>.
21+
*
22+
* To disable strict typing, comment out the directive below.
23+
*/
24+
25+
declare(strict_types=1);
26+
27+
class BufferFullError extends Exception
28+
{
29+
}
30+
31+
class BufferEmptyError extends Exception
32+
{
33+
}
34+
35+
class CircularBuffer
36+
{
37+
private int $capacity;
38+
private array $buffer;
39+
private int $readPosition;
40+
private int $writePosition;
41+
42+
public function __construct($capacity)
43+
{
44+
$this->capacity = $capacity;
45+
$this->buffer = array_fill(0, $capacity, null);
46+
$this->readPosition = 0;
47+
$this->writePosition = 0;
48+
}
49+
50+
/**
51+
* @throws BufferEmptyError
52+
*/
53+
public function read()
54+
{
55+
if ($this->isEmpty()) {
56+
throw new BufferEmptyError();
57+
}
58+
$value = $this->buffer[$this->readPosition];
59+
$this->buffer[$this->readPosition] = null;
60+
$this->readPosition = ($this->readPosition + 1) % $this->capacity;
61+
return $value;
62+
}
63+
64+
/**
65+
* @throws BufferFullError
66+
*/
67+
public function write($item): void
68+
{
69+
if ($this->isFull()) {
70+
throw new BufferFullError();
71+
}
72+
$this->buffer[$this->writePosition] = $item;
73+
$this->writePosition = ($this->writePosition + 1) % $this->capacity;
74+
}
75+
76+
/**
77+
* @throws BufferEmptyError
78+
* @throws BufferFullError
79+
*/
80+
public function forceWrite($item): void
81+
{
82+
if ($this->isFull()) {
83+
$this->read();
84+
}
85+
$this->write($item);
86+
}
87+
88+
public function clear(): void
89+
{
90+
$this->buffer = array_fill(0, $this->capacity, null);
91+
$this->readPosition = 0;
92+
$this->writePosition = 0;
93+
}
94+
95+
private function isEmpty(): bool
96+
{
97+
return $this->buffer[$this->readPosition] === null;
98+
}
99+
100+
private function isFull(): bool
101+
{
102+
return $this->buffer[$this->writePosition] !== null;
103+
}
104+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# This is an auto-generated file.
2+
#
3+
# Regenerating this file via `configlet sync` will:
4+
# - Recreate every `description` key/value pair
5+
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
6+
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
7+
# - Preserve any other key/value pair
8+
#
9+
# As user-added comments (using the # character) will be removed when this file
10+
# is regenerated, comments can be added via a `comment` key.
11+
12+
[28268ed4-4ff3-45f3-820e-895b44d53dfa]
13+
description = "reading empty buffer should fail"
14+
15+
[2e6db04a-58a1-425d-ade8-ac30b5f318f3]
16+
description = "can read an item just written"
17+
18+
[90741fe8-a448-45ce-be2b-de009a24c144]
19+
description = "each item may only be read once"
20+
21+
[be0e62d5-da9c-47a8-b037-5db21827baa7]
22+
description = "items are read in the order they are written"
23+
24+
[2af22046-3e44-4235-bfe6-05ba60439d38]
25+
description = "full buffer can't be written to"
26+
27+
[547d192c-bbf0-4369-b8fa-fc37e71f2393]
28+
description = "a read frees up capacity for another write"
29+
30+
[04a56659-3a81-4113-816b-6ecb659b4471]
31+
description = "read position is maintained even across multiple writes"
32+
33+
[60c3a19a-81a7-43d7-bb0a-f07242b1111f]
34+
description = "items cleared out of buffer can't be read"
35+
36+
[45f3ae89-3470-49f3-b50e-362e4b330a59]
37+
description = "clear frees up capacity for another write"
38+
39+
[e1ac5170-a026-4725-bfbe-0cf332eddecd]
40+
description = "clear does nothing on empty buffer"
41+
42+
[9c2d4f26-3ec7-453f-a895-7e7ff8ae7b5b]
43+
description = "overwrite acts like write on non-full buffer"
44+
45+
[880f916b-5039-475c-bd5c-83463c36a147]
46+
description = "overwrite replaces the oldest item on full buffer"
47+
48+
[bfecab5b-aca1-4fab-a2b0-cd4af2b053c3]
49+
description = "overwrite replaces the oldest item remaining in buffer following a read"
50+
51+
[9cebe63a-c405-437b-8b62-e3fdc1ecec5a]
52+
description = "initial clear does not affect wrapping around"
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?php
2+
3+
/*
4+
* By adding type hints and enabling strict type checking, code can become
5+
* easier to read, self-documenting and reduce the number of potential bugs.
6+
* By default, type declarations are non-strict, which means they will attempt
7+
* to change the original type to match the type specified by the
8+
* type-declaration.
9+
*
10+
* In other words, if you pass a string to a function requiring a float,
11+
* it will attempt to convert the string value to a float.
12+
*
13+
* To enable strict mode, a single declare directive must be placed at the top
14+
* of the file.
15+
* This means that the strictness of typing is configured on a per-file basis.
16+
* This directive not only affects the type declarations of parameters, but also
17+
* a function's return type.
18+
*
19+
* For more info review the Concept on strict type checking in the PHP track
20+
* <link>.
21+
*
22+
* To disable strict typing, comment out the directive below.
23+
*/
24+
25+
declare(strict_types=1);
26+
27+
class BufferFullError extends Exception
28+
{
29+
}
30+
31+
class BufferEmptyError extends Exception
32+
{
33+
}
34+
35+
class CircularBuffer
36+
{
37+
public function read()
38+
{
39+
throw new \BadMethodCallException(sprintf('Implement the %s method', __FUNCTION__));
40+
}
41+
42+
public function write($item): void
43+
{
44+
throw new \BadMethodCallException(sprintf('Implement the %s method', __FUNCTION__));
45+
}
46+
47+
public function forceWrite($item): void
48+
{
49+
throw new \BadMethodCallException(sprintf('Implement the %s method', __FUNCTION__));
50+
}
51+
52+
public function clear(): void
53+
{
54+
throw new \BadMethodCallException(sprintf('Implement the %s method', __FUNCTION__));
55+
}
56+
}

0 commit comments

Comments
 (0)