diff --git a/config.json b/config.json index be183d8..60983e8 100644 --- a/config.json +++ b/config.json @@ -530,6 +530,14 @@ "prerequisites": [], "difficulty": 5 }, + { + "slug": "circular-buffer", + "name": "Circular Buffer", + "uuid": "1ef26e0a-c4de-4250-999e-0ab72c57f3cf", + "practices": [], + "prerequisites": [], + "difficulty": 5 + }, { "slug": "flower-field", "name": "Flower Field", diff --git a/exercises/practice/circular-buffer/.busted b/exercises/practice/circular-buffer/.busted new file mode 100644 index 0000000..86b84e7 --- /dev/null +++ b/exercises/practice/circular-buffer/.busted @@ -0,0 +1,5 @@ +return { + default = { + ROOT = { '.' } + } +} 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..6ec0c54 --- /dev/null +++ b/exercises/practice/circular-buffer/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "glennj" + ], + "files": { + "solution": [ + "circular_buffer.moon" + ], + "test": [ + "circular_buffer_spec.moon" + ], + "example": [ + ".meta/example.moon" + ] + }, + "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.moon b/exercises/practice/circular-buffer/.meta/example.moon new file mode 100644 index 0000000..8f85e9c --- /dev/null +++ b/exercises/practice/circular-buffer/.meta/example.moon @@ -0,0 +1,31 @@ +class CircularBuffer + new: (@size) => + @buff = [0 for _ = 1, @size] + @idx = r: 1, w: 1 + @count = 0 + + read: => + return nil, false if @count == 0 + item = @buff[@idx.r] + @idx.r = @idx.r == @size and 1 or @idx.r + 1 + @count -= 1 + item, true + + write: (item, options={}) => + if @count == @size + if options.overwrite + @read! + else + return false + + @buff[@idx.w] = item + @idx.w = @idx.w == @size and 1 or @idx.w + 1 + @count += 1 + true + + clear: => + @count = 0 + @idx.r = @idx.w + + +CircularBuffer diff --git a/exercises/practice/circular-buffer/.meta/spec_generator.moon b/exercises/practice/circular-buffer/.meta/spec_generator.moon new file mode 100644 index 0000000..bf0c3d0 --- /dev/null +++ b/exercises/practice/circular-buffer/.meta/spec_generator.moon @@ -0,0 +1,25 @@ +{ + module_name: 'CircularBuffer', + + generate_test: (case, level) -> + lines = { "buffer = CircularBuffer #{case.input.capacity}" } + for op in *case.input.operations + switch op.operation + when 'read' + table.insert lines, 'value, ok = buffer\\read!' + table.insert lines, "assert.is_#{op.should_succeed} ok" + if op.should_succeed + table.insert lines, "assert.are.equal #{op.expected}, value" + + when 'write' + table.insert lines, "ok = buffer\\write #{op.item}" + table.insert lines, "assert.is_#{op.should_succeed} ok" + + when 'clear' + table.insert lines, 'buffer\\clear!' + + when 'overwrite' + table.insert lines, "buffer\\write #{op.item}, overwrite: true" + + table.concat [indent line, level for line in *lines], '\n' +} 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.moon b/exercises/practice/circular-buffer/circular_buffer.moon new file mode 100644 index 0000000..c79136f --- /dev/null +++ b/exercises/practice/circular-buffer/circular_buffer.moon @@ -0,0 +1,15 @@ +class CircularBuffer + new: (size) => + error 'Implement the constructor' + + read: => + error 'Implement the read method' + + write: (item) => + error 'Implement the write method' + + clear: => + error 'Implement the clear method' + + +CircularBuffer diff --git a/exercises/practice/circular-buffer/circular_buffer_spec.moon b/exercises/practice/circular-buffer/circular_buffer_spec.moon new file mode 100644 index 0000000..7ac47c0 --- /dev/null +++ b/exercises/practice/circular-buffer/circular_buffer_spec.moon @@ -0,0 +1,172 @@ +CircularBuffer = require 'circular_buffer' + +describe 'circular-buffer', -> + it 'reading empty buffer should fail', -> + buffer = CircularBuffer 1 + value, ok = buffer\read! + assert.is_false ok + + pending 'can read an item just written', -> + buffer = CircularBuffer 1 + ok = buffer\write 1 + assert.is_true ok + value, ok = buffer\read! + assert.is_true ok + assert.are.equal 1, value + + pending 'each item may only be read once', -> + buffer = CircularBuffer 1 + ok = buffer\write 1 + assert.is_true ok + value, ok = buffer\read! + assert.is_true ok + assert.are.equal 1, value + value, ok = buffer\read! + assert.is_false ok + + pending 'items are read in the order they are written', -> + buffer = CircularBuffer 2 + ok = buffer\write 1 + assert.is_true ok + ok = buffer\write 2 + assert.is_true ok + value, ok = buffer\read! + assert.is_true ok + assert.are.equal 1, value + value, ok = buffer\read! + assert.is_true ok + assert.are.equal 2, value + + pending "full buffer can't be written to", -> + buffer = CircularBuffer 1 + ok = buffer\write 1 + assert.is_true ok + ok = buffer\write 2 + assert.is_false ok + + pending 'a read frees up capacity for another write', -> + buffer = CircularBuffer 1 + ok = buffer\write 1 + assert.is_true ok + value, ok = buffer\read! + assert.is_true ok + assert.are.equal 1, value + ok = buffer\write 2 + assert.is_true ok + value, ok = buffer\read! + assert.is_true ok + assert.are.equal 2, value + + pending 'read position is maintained even across multiple writes', -> + buffer = CircularBuffer 3 + ok = buffer\write 1 + assert.is_true ok + ok = buffer\write 2 + assert.is_true ok + value, ok = buffer\read! + assert.is_true ok + assert.are.equal 1, value + ok = buffer\write 3 + assert.is_true ok + value, ok = buffer\read! + assert.is_true ok + assert.are.equal 2, value + value, ok = buffer\read! + assert.is_true ok + assert.are.equal 3, value + + pending "items cleared out of buffer can't be read", -> + buffer = CircularBuffer 1 + ok = buffer\write 1 + assert.is_true ok + buffer\clear! + value, ok = buffer\read! + assert.is_false ok + + pending 'clear frees up capacity for another write', -> + buffer = CircularBuffer 1 + ok = buffer\write 1 + assert.is_true ok + buffer\clear! + ok = buffer\write 2 + assert.is_true ok + value, ok = buffer\read! + assert.is_true ok + assert.are.equal 2, value + + pending 'clear does nothing on empty buffer', -> + buffer = CircularBuffer 1 + buffer\clear! + ok = buffer\write 1 + assert.is_true ok + value, ok = buffer\read! + assert.is_true ok + assert.are.equal 1, value + + pending 'overwrite acts like write on non-full buffer', -> + buffer = CircularBuffer 2 + ok = buffer\write 1 + assert.is_true ok + buffer\write 2, overwrite: true + value, ok = buffer\read! + assert.is_true ok + assert.are.equal 1, value + value, ok = buffer\read! + assert.is_true ok + assert.are.equal 2, value + + pending 'overwrite replaces the oldest item on full buffer', -> + buffer = CircularBuffer 2 + ok = buffer\write 1 + assert.is_true ok + ok = buffer\write 2 + assert.is_true ok + buffer\write 3, overwrite: true + value, ok = buffer\read! + assert.is_true ok + assert.are.equal 2, value + value, ok = buffer\read! + assert.is_true ok + assert.are.equal 3, value + + pending 'overwrite replaces the oldest item remaining in buffer following a read', -> + buffer = CircularBuffer 3 + ok = buffer\write 1 + assert.is_true ok + ok = buffer\write 2 + assert.is_true ok + ok = buffer\write 3 + assert.is_true ok + value, ok = buffer\read! + assert.is_true ok + assert.are.equal 1, value + ok = buffer\write 4 + assert.is_true ok + buffer\write 5, overwrite: true + value, ok = buffer\read! + assert.is_true ok + assert.are.equal 3, value + value, ok = buffer\read! + assert.is_true ok + assert.are.equal 4, value + value, ok = buffer\read! + assert.is_true ok + assert.are.equal 5, value + + pending 'initial clear does not affect wrapping around', -> + buffer = CircularBuffer 2 + buffer\clear! + ok = buffer\write 1 + assert.is_true ok + ok = buffer\write 2 + assert.is_true ok + buffer\write 3, overwrite: true + buffer\write 4, overwrite: true + value, ok = buffer\read! + assert.is_true ok + assert.are.equal 3, value + value, ok = buffer\read! + assert.is_true ok + assert.are.equal 4, value + value, ok = buffer\read! + assert.is_false ok