From 2ac868ffc28005b87cb1ff51601bbaac24fbaa36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20B=20Nagy?= <20251272+BNAndras@users.noreply.github.com> Date: Tue, 12 Mar 2024 13:55:45 -0700 Subject: [PATCH] Add circular buffer --- config.json | 8 + .../circular-buffer/.docs/instructions.md | 58 ++++++++ .../circular-buffer/.meta/config.json | 19 +++ .../circular-buffer/.meta/example.arr | 43 ++++++ .../practice/circular-buffer/.meta/tests.toml | 52 +++++++ .../circular-buffer/circular-buffer-test.arr | 140 ++++++++++++++++++ .../circular-buffer/circular-buffer.arr | 1 + 7 files changed, 321 insertions(+) create mode 100644 exercises/practice/circular-buffer/.docs/instructions.md create mode 100644 exercises/practice/circular-buffer/.meta/config.json create mode 100644 exercises/practice/circular-buffer/.meta/example.arr create mode 100644 exercises/practice/circular-buffer/.meta/tests.toml create mode 100644 exercises/practice/circular-buffer/circular-buffer-test.arr create mode 100644 exercises/practice/circular-buffer/circular-buffer.arr diff --git a/config.json b/config.json index a46a0af..d7bec09 100644 --- a/config.json +++ b/config.json @@ -103,6 +103,14 @@ "prerequisites": [], "difficulty": 2 }, + { + "slug": "circular-buffer", + "name": "Circular Buffer", + "uuid": "512d263f-b3ce-4f5a-ba43-6b05a2b5c0f5", + "practices": [], + "prerequisites": [], + "difficulty": 3 + }, { "slug": "clock", "name": "Clock", diff --git a/exercises/practice/circular-buffer/.docs/instructions.md b/exercises/practice/circular-buffer/.docs/instructions.md new file mode 100644 index 0000000..2ba1fda --- /dev/null +++ b/exercises/practice/circular-buffer/.docs/instructions.md @@ -0,0 +1,58 @@ +# Instructions + +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. + +A circular buffer first starts empty and of some predefined length. +For example, this is a 7-element buffer: + +```text +[ ][ ][ ][ ][ ][ ][ ] +``` + +Assume that a 1 is written into the middle of the buffer (exact starting location does not matter in a circular buffer): + +```text +[ ][ ][ ][1][ ][ ][ ] +``` + +Then assume that two more elements are added — 2 & 3 — which get appended after the 1: + +```text +[ ][ ][ ][1][2][3][ ] +``` + +If two elements are then removed from the buffer, the oldest values inside the buffer are removed. +The two elements removed, in this case, are 1 & 2, leaving the buffer with just a 3: + +```text +[ ][ ][ ][ ][ ][3][ ] +``` + +If the buffer has 7 elements then it is completely full: + +```text +[5][6][7][8][9][3][4] +``` + +When the buffer is full an error will be raised, alerting the client that further writes are blocked until a slot becomes free. + +When the buffer is full, the client can opt to overwrite the oldest data with a forced write. +In this case, two more elements — A & B — are added and they overwrite the 3 & 4: + +```text +[5][6][7][8][9][A][B] +``` + +3 & 4 have been replaced by A & B making 5 now the oldest data in the buffer. +Finally, if two elements are removed then what would be returned is 5 & 6 yielding the buffer: + +```text +[ ][ ][7][8][9][A][B] +``` + +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. +7 is still the oldest element and the buffer is once again full. + +```text +[C][D][7][8][9][A][B] +``` diff --git a/exercises/practice/circular-buffer/.meta/config.json b/exercises/practice/circular-buffer/.meta/config.json new file mode 100644 index 0000000..2a64bfd --- /dev/null +++ b/exercises/practice/circular-buffer/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "BNAndras" + ], + "files": { + "solution": [ + "circular-buffer.arr" + ], + "test": [ + "circular-buffer-test.arr" + ], + "example": [ + ".meta/example.arr" + ] + }, + "blurb": "A data structure that uses a single, fixed-size buffer as if it were connected end-to-end.", + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Circular_buffer" +} diff --git a/exercises/practice/circular-buffer/.meta/example.arr b/exercises/practice/circular-buffer/.meta/example.arr new file mode 100644 index 0000000..09e78ba --- /dev/null +++ b/exercises/practice/circular-buffer/.meta/example.arr @@ -0,0 +1,43 @@ +use context essentials2020 # Don't delete this line when using Pyret on Exercism + +data CircularBuffer: + | buffer(capacity :: Number, size :: Number, elements, read-index :: Number, write-index :: Number) + with: + method write(self, value :: Number) block: + when self.size == self.capacity: + raise(“full buffer”) + end + self.elements.set-now(self.write-index, value) + buffer(self.capacity, + self.size + 1, + self.elements, + self.read-index, + num-modulo(self.read-index + 1, self.capacity)) + end, + method read(self) block: + when self.size == 0: + raise(“empty buffer”) + end + val = self.elements.get-now(self.read-index) + updated = buffer(self.capacity, + self.size - 1, + self.elements, + num-modulo(self.read-index + 1, self.capacity), + self.write-index) + {val;updated} + end, + method overwrite(self, value :: Number) block: + buff = if self.size == self.capacity: + self.read().{1} + else: + self + end + buff.write(value) + end, + method clear(self): + make-buffer(self.capacity) + end +end +fun make-buffer(capacity :: Number): + buffer(capacity, 0, array-of(none, capacity), 0, 0) +end \ No newline at end of file diff --git a/exercises/practice/circular-buffer/.meta/tests.toml b/exercises/practice/circular-buffer/.meta/tests.toml new file mode 100644 index 0000000..0fb3143 --- /dev/null +++ b/exercises/practice/circular-buffer/.meta/tests.toml @@ -0,0 +1,52 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[28268ed4-4ff3-45f3-820e-895b44d53dfa] +description = "reading empty buffer should fail" + +[2e6db04a-58a1-425d-ade8-ac30b5f318f3] +description = "can read an item just written" + +[90741fe8-a448-45ce-be2b-de009a24c144] +description = "each item may only be read once" + +[be0e62d5-da9c-47a8-b037-5db21827baa7] +description = "items are read in the order they are written" + +[2af22046-3e44-4235-bfe6-05ba60439d38] +description = "full buffer can't be written to" + +[547d192c-bbf0-4369-b8fa-fc37e71f2393] +description = "a read frees up capacity for another write" + +[04a56659-3a81-4113-816b-6ecb659b4471] +description = "read position is maintained even across multiple writes" + +[60c3a19a-81a7-43d7-bb0a-f07242b1111f] +description = "items cleared out of buffer can't be read" + +[45f3ae89-3470-49f3-b50e-362e4b330a59] +description = "clear frees up capacity for another write" + +[e1ac5170-a026-4725-bfbe-0cf332eddecd] +description = "clear does nothing on empty buffer" + +[9c2d4f26-3ec7-453f-a895-7e7ff8ae7b5b] +description = "overwrite acts like write on non-full buffer" + +[880f916b-5039-475c-bd5c-83463c36a147] +description = "overwrite replaces the oldest item on full buffer" + +[bfecab5b-aca1-4fab-a2b0-cd4af2b053c3] +description = "overwrite replaces the oldest item remaining in buffer following a read" + +[9cebe63a-c405-437b-8b62-e3fdc1ecec5a] +description = "initial clear does not affect wrapping around" diff --git a/exercises/practice/circular-buffer/circular-buffer-test.arr b/exercises/practice/circular-buffer/circular-buffer-test.arr new file mode 100644 index 0000000..d8b6030 --- /dev/null +++ b/exercises/practice/circular-buffer/circular-buffer-test.arr @@ -0,0 +1,140 @@ +use context essentials2020 + +include file("circular-buffer.arr") + +#| + When working offline, all tests except the first one are skipped by default. + Once you get the first test running, unskip the next one until all tests pass locally. + Check the block comment below for further details. +|# + +fun read-empty-buffer-should-fail(): + check "reading empty buffer should fail": + make-buffer(1).read() raises "empty buffer" + end +end + +fun can-read-an-item-just-written(): + check "can read an item just written": + make-buffer(1).write(1).read() is 1 + end +end + +fun each-item-may-only-be-read-once(): + check "each item may only be read once": + results = make-buffer(1).write(1).read() + results.{0} is 1 + results.{1}.read() raises "empty buffer" + end +end + +fun items-are-read-in-the-order-they-are-written(): + check "items are read in the order they are written": + var results = make-buffer(2).write(1).write(2).read() + results.{0} is 1 + results = results.{1}.read() + results.{0} is 2 + end +end + +fun full-buffer-can-not-be-written-to(): + check "full buffer can't be written to": + make-buffer(1).write(1).write(2) raises "full buffer" + end +end + +fun a-read-frees-up-capacity-for-another-write(): + check "a read frees up capacity for another write": + var results = make-buffer(1).write(1).read() + results.{0} is 1 + results = results.{1}.write(2).read() + results.{0} is 2 + end +end + +fun read-position-is-maintained-even-across-multiple-writes(): + check "read position is maintained even across multiple writes": + var results = make-buffer(3).write(1).write(2).read() + results.{0} is 1 + results = results.{1}.write(3).read() + results.{0} is 2 + results = results.{1}.read() + results.{0} is 3 + end +end + +fun items-cleared-out-of-buffer-can-not-be-read(): + check "items cleared out of buffer can't be read": + results = make-buffer(1).write(1).clear() + results.read() raises "empty buffer" + end +end + +fun clear-frees-up-capacity-for-another-write(): + check "clear frees up capacity for another write": + results = make-buffer(1).write(1).clear().write(2) + results.read() is 2 + end +end + +fun clear-does-nothing-on-empty-buffer(): + check "clear does nothing on empty buffer": + results = make-buffer(1).clear().write(1) + results.read() is 1 + end +end + +fun overwrite-acts-like-write-on-non-full-buffer(): + check "overwrite acts like write on non-full buffer": + ... + end +end + +fun overwrite-replaces-the-oldest-item-on-full-buffer(): + check "overwrite replaces the oldest item on full buffer": + ... + end +end + +fun overwrite-replaces-the-oldest-item-remaining-in-buffer-following-a-read(): + check "overwrite replaces the oldest item remaining in buffer following a read": + ... + end +end + +fun initial-clear-does-not-affect-wrapping-around(): + check "initial clear does not affect wrapping around": + ... + end +end + +#| + Code to run each test. Each line corresponds to a test above and whether it should be run. + To mark a test to be run, replace `false` with `true` on that same line after the comma. + test(test-a, true) will be run. test(test-a, false) will be skipped. +|# + +data TestRun: test(run, active) end + +[list: + test(read-empty-buffer-should-fail, true), + test(can-read-an-item-just-written, false), + test(each-item-may-only-be-read-once, false), + test(items-are-read-in-the-order-they-are-written, false), + test(full-buffer-can-not-be-written-to, false), + test(a-read-frees-up-capacity-for-another-write, false), + test(read-position-is-maintained-even-across-multiple-writes, false), + test(items-cleared-out-of-buffer-can-not-be-read, false), + test(clear-frees-up-capacity-for-another-write, false), + test(clear-does-nothing-on-empty-buffer, false), + test(overwrite-acts-like-write-on-non-full-buffer, false), + test(overwrite-replaces-the-oldest-item-on-full-buffer, false), + test(overwrite-replaces-the-oldest-item-remaining-in-buffer-following-a-read, false), + test(initial-clear-does-not-affect-wrapping-around, false), + test(zero-steps-for-one, true), + test(divide-if-even, false), + test(even-and-odd-steps, false), + test(large-number-of-even-and-odd-steps, false), + test(zero-is-an-error, false), + test(negative-is-an-error, false) +].each(lam(t): when t.active: t.run() end end) diff --git a/exercises/practice/circular-buffer/circular-buffer.arr b/exercises/practice/circular-buffer/circular-buffer.arr new file mode 100644 index 0000000..8e85180 --- /dev/null +++ b/exercises/practice/circular-buffer/circular-buffer.arr @@ -0,0 +1 @@ +use context essentials2020 # Don't delete this line when using Pyret on Exercism \ No newline at end of file