diff --git a/config.json b/config.json index 79dac992..1c8b2189 100644 --- a/config.json +++ b/config.json @@ -19,10 +19,18 @@ "average_run_time": 4 }, "files": { - "solution": ["src/main/kotlin/%{pascal_slug}.kt"], - "test": ["src/test/kotlin/%{pascal_slug}Test.kt"], - "example": [".meta/src/reference/kotlin/%{pascal_slug}.kt"], - "exemplar": [".meta/src/reference/kotlin/%{pascal_slug}.kt"] + "solution": [ + "src/main/kotlin/%{pascal_slug}.kt" + ], + "test": [ + "src/test/kotlin/%{pascal_slug}Test.kt" + ], + "example": [ + ".meta/src/reference/kotlin/%{pascal_slug}.kt" + ], + "exemplar": [ + ".meta/src/reference/kotlin/%{pascal_slug}.kt" + ] }, "exercises": { "concept": [ @@ -30,7 +38,9 @@ "slug": "lucians-luscious-lasagna", "name": "Lucian's Luscious Lasagna", "uuid": "de14936d-28d7-4531-9761-69deba31a7ca", - "concepts": ["basics"], + "concepts": [ + "basics" + ], "prerequisites": [], "status": "wip" }, @@ -38,8 +48,12 @@ "slug": "annalyns-infiltration", "name": "Annalyn's Infiltration", "uuid": "5a0af671-2549-411c-860a-f36859b429e5", - "concepts": ["booleans"], - "prerequisites": ["basics"], + "concepts": [ + "booleans" + ], + "prerequisites": [ + "basics" + ], "status": "wip" } ], @@ -51,7 +65,9 @@ "practices": [], "prerequisites": [], "difficulty": 1, - "topics": ["strings"] + "topics": [ + "strings" + ] }, { "slug": "two-fer", @@ -60,7 +76,10 @@ "practices": [], "prerequisites": [], "difficulty": 2, - "topics": ["conditionals", "strings"] + "topics": [ + "conditionals", + "strings" + ] }, { "slug": "hamming", @@ -69,7 +88,11 @@ "practices": [], "prerequisites": [], "difficulty": 3, - "topics": ["integers", "loops", "strings"] + "topics": [ + "integers", + "loops", + "strings" + ] }, { "slug": "gigasecond", @@ -78,7 +101,11 @@ "practices": [], "prerequisites": [], "difficulty": 3, - "topics": ["dates", "integers", "time"] + "topics": [ + "dates", + "integers", + "time" + ] }, { "slug": "scrabble-score", @@ -117,7 +144,11 @@ "practices": [], "prerequisites": [], "difficulty": 3, - "topics": ["integers", "loops", "math"] + "topics": [ + "integers", + "loops", + "math" + ] }, { "slug": "secret-handshake", @@ -172,7 +203,10 @@ "practices": [], "prerequisites": [], "difficulty": 5, - "topics": ["stacks", "strings"] + "topics": [ + "stacks", + "strings" + ] }, { "slug": "word-count", @@ -181,7 +215,13 @@ "practices": [], "prerequisites": [], "difficulty": 5, - "topics": ["conditionals", "integers", "loops", "maps", "strings"] + "topics": [ + "conditionals", + "integers", + "loops", + "maps", + "strings" + ] }, { "slug": "robot-name", @@ -205,7 +245,11 @@ "practices": [], "prerequisites": [], "difficulty": 5, - "topics": ["cryptography", "integers", "strings"] + "topics": [ + "cryptography", + "integers", + "strings" + ] }, { "slug": "bank-account", @@ -214,7 +258,11 @@ "practices": [], "prerequisites": [], "difficulty": 6, - "topics": ["concurrency", "exception_handling", "integers"] + "topics": [ + "concurrency", + "exception_handling", + "integers" + ] }, { "slug": "linked-list", @@ -223,7 +271,11 @@ "practices": [], "prerequisites": [], "difficulty": 6, - "topics": ["algorithms", "generics", "lists"] + "topics": [ + "algorithms", + "generics", + "lists" + ] }, { "slug": "binary-search", @@ -232,7 +284,12 @@ "practices": [], "prerequisites": [], "difficulty": 6, - "topics": ["arrays", "generics", "recursion", "searching"] + "topics": [ + "arrays", + "generics", + "recursion", + "searching" + ] }, { "slug": "pangram", @@ -241,7 +298,11 @@ "practices": [], "prerequisites": [], "difficulty": 3, - "topics": ["pattern_matching", "regular_expressions", "strings"] + "topics": [ + "pattern_matching", + "regular_expressions", + "strings" + ] }, { "slug": "raindrops", @@ -250,7 +311,11 @@ "practices": [], "prerequisites": [], "difficulty": 3, - "topics": ["conditionals", "integers", "strings"] + "topics": [ + "conditionals", + "integers", + "strings" + ] }, { "slug": "isogram", @@ -259,7 +324,12 @@ "practices": [], "prerequisites": [], "difficulty": 4, - "topics": ["conditionals", "loops", "parsing", "strings"] + "topics": [ + "conditionals", + "loops", + "parsing", + "strings" + ] }, { "slug": "protein-translation", @@ -268,7 +338,13 @@ "practices": [], "prerequisites": [], "difficulty": 2, - "topics": ["algorithms", "lists", "conditionals", "loops", "strings"] + "topics": [ + "algorithms", + "lists", + "conditionals", + "loops", + "strings" + ] }, { "slug": "pig-latin", @@ -277,7 +353,12 @@ "practices": [], "prerequisites": [], "difficulty": 5, - "topics": ["arrays", "lists", "strings", "transforming"] + "topics": [ + "arrays", + "lists", + "strings", + "transforming" + ] }, { "slug": "anagram", @@ -302,7 +383,9 @@ "practices": [], "prerequisites": [], "difficulty": 2, - "topics": ["lists"] + "topics": [ + "lists" + ] }, { "slug": "resistor-color-duo", @@ -311,7 +394,9 @@ "practices": [], "prerequisites": [], "difficulty": 2, - "topics": ["lists"] + "topics": [ + "lists" + ] }, { "slug": "rna-transcription", @@ -320,7 +405,11 @@ "practices": [], "prerequisites": [], "difficulty": 2, - "topics": ["loops", "maps", "strings"] + "topics": [ + "loops", + "maps", + "strings" + ] }, { "slug": "diamond", @@ -329,7 +418,13 @@ "practices": [], "prerequisites": [], "difficulty": 4, - "topics": ["arrays", "lists", "loops", "strings", "text_formatting"] + "topics": [ + "arrays", + "lists", + "loops", + "strings", + "text_formatting" + ] }, { "slug": "bob", @@ -338,7 +433,11 @@ "practices": [], "prerequisites": [], "difficulty": 5, - "topics": ["booleans", "conditionals", "strings"] + "topics": [ + "booleans", + "conditionals", + "strings" + ] }, { "slug": "beer-song", @@ -362,7 +461,12 @@ "practices": [], "prerequisites": [], "difficulty": 3, - "topics": ["loops", "parsing", "searching", "strings"] + "topics": [ + "loops", + "parsing", + "searching", + "strings" + ] }, { "slug": "armstrong-numbers", @@ -371,7 +475,11 @@ "practices": [], "prerequisites": [], "difficulty": 3, - "topics": ["algorithms", "loops", "math"] + "topics": [ + "algorithms", + "loops", + "math" + ] }, { "slug": "resistor-color-trio", @@ -380,7 +488,11 @@ "practices": [], "prerequisites": [], "difficulty": 3, - "topics": ["math", "type_conversion", "lists"] + "topics": [ + "math", + "type_conversion", + "lists" + ] }, { "slug": "transpose", @@ -389,7 +501,12 @@ "practices": [], "prerequisites": [], "difficulty": 3, - "topics": ["lists", "loops", "maps", "strings"] + "topics": [ + "lists", + "loops", + "maps", + "strings" + ] }, { "slug": "isbn-verifier", @@ -398,7 +515,11 @@ "practices": [], "prerequisites": [], "difficulty": 4, - "topics": ["integers", "loops", "strings"] + "topics": [ + "integers", + "loops", + "strings" + ] }, { "slug": "largest-series-product", @@ -407,7 +528,13 @@ "practices": [], "prerequisites": [], "difficulty": 4, - "topics": ["integers", "loops", "math", "strings", "type_conversion"] + "topics": [ + "integers", + "loops", + "math", + "strings", + "type_conversion" + ] }, { "slug": "luhn", @@ -477,7 +604,13 @@ "practices": [], "prerequisites": [], "difficulty": 6, - "topics": ["integers", "logic", "loops", "maps", "strings"] + "topics": [ + "integers", + "logic", + "loops", + "maps", + "strings" + ] }, { "slug": "leap", @@ -486,7 +619,10 @@ "practices": [], "prerequisites": [], "difficulty": 2, - "topics": ["conditionals", "integers"] + "topics": [ + "conditionals", + "integers" + ] }, { "slug": "custom-set", @@ -495,7 +631,11 @@ "practices": [], "prerequisites": [], "difficulty": 3, - "topics": ["object_oriented_programming", "sets", "varargs"] + "topics": [ + "object_oriented_programming", + "sets", + "varargs" + ] }, { "slug": "space-age", @@ -504,7 +644,10 @@ "practices": [], "prerequisites": [], "difficulty": 3, - "topics": ["conditionals", "floating_point_numbers"] + "topics": [ + "conditionals", + "floating_point_numbers" + ] }, { "slug": "allergies", @@ -529,7 +672,12 @@ "practices": [], "prerequisites": [], "difficulty": 7, - "topics": ["conditionals", "dates", "enumerations", "loops"] + "topics": [ + "conditionals", + "dates", + "enumerations", + "loops" + ] }, { "slug": "crypto-square", @@ -538,7 +686,11 @@ "practices": [], "prerequisites": [], "difficulty": 3, - "topics": ["strings", "text_formatting", "transforming"] + "topics": [ + "strings", + "text_formatting", + "transforming" + ] }, { "slug": "run-length-encoding", @@ -562,7 +714,10 @@ "practices": [], "prerequisites": [], "difficulty": 3, - "topics": ["lists", "strings"] + "topics": [ + "lists", + "strings" + ] }, { "slug": "wordy", @@ -571,7 +726,12 @@ "practices": [], "prerequisites": [], "difficulty": 6, - "topics": ["loops", "pattern_matching", "strings", "recursion"] + "topics": [ + "loops", + "pattern_matching", + "strings", + "recursion" + ] }, { "slug": "minesweeper", @@ -596,7 +756,12 @@ "practices": [], "prerequisites": [], "difficulty": 7, - "topics": ["algorithms", "exception_handling", "games", "lists"] + "topics": [ + "algorithms", + "exception_handling", + "games", + "lists" + ] }, { "slug": "perfect-numbers", @@ -634,7 +799,13 @@ "practices": [], "prerequisites": [], "difficulty": 4, - "topics": ["algorithms", "integers", "lists", "loops", "math"] + "topics": [ + "algorithms", + "integers", + "lists", + "loops", + "math" + ] }, { "slug": "sum-of-multiples", @@ -643,7 +814,13 @@ "practices": [], "prerequisites": [], "difficulty": 4, - "topics": ["arrays", "conditionals", "integers", "loops", "math"] + "topics": [ + "arrays", + "conditionals", + "integers", + "loops", + "math" + ] }, { "slug": "forth", @@ -652,7 +829,10 @@ "practices": [], "prerequisites": [], "difficulty": 4, - "topics": ["pattern_matching", "integers"] + "topics": [ + "pattern_matching", + "integers" + ] }, { "slug": "grains", @@ -661,7 +841,9 @@ "practices": [], "prerequisites": [], "difficulty": 4, - "topics": ["integers"] + "topics": [ + "integers" + ] }, { "slug": "robot-simulator", @@ -670,7 +852,12 @@ "practices": [], "prerequisites": [], "difficulty": 6, - "topics": ["classes", "enumerations", "logic", "loops"] + "topics": [ + "classes", + "enumerations", + "logic", + "loops" + ] }, { "slug": "collatz-conjecture", @@ -759,7 +946,10 @@ "practices": [], "prerequisites": [], "difficulty": 8, - "topics": ["floating_point_numbers", "math"] + "topics": [ + "floating_point_numbers", + "math" + ] }, { "slug": "all-your-base", @@ -800,7 +990,10 @@ "practices": [], "prerequisites": [], "difficulty": 5, - "topics": ["stacks", "strings"] + "topics": [ + "stacks", + "strings" + ] }, { "slug": "spiral-matrix", @@ -809,7 +1002,12 @@ "practices": [], "prerequisites": [], "difficulty": 5, - "topics": ["arrays", "integers", "loops", "matrices"] + "topics": [ + "arrays", + "integers", + "loops", + "matrices" + ] }, { "slug": "change", @@ -818,7 +1016,12 @@ "practices": [], "prerequisites": [], "difficulty": 8, - "topics": ["algorithms", "exception_handling", "integers", "lists"] + "topics": [ + "algorithms", + "exception_handling", + "integers", + "lists" + ] }, { "slug": "binary-search-tree", @@ -827,7 +1030,12 @@ "practices": [], "prerequisites": [], "difficulty": 5, - "topics": ["arrays", "generics", "recursion", "searching"] + "topics": [ + "arrays", + "generics", + "recursion", + "searching" + ] }, { "slug": "bowling", @@ -847,7 +1055,11 @@ "practices": [], "prerequisites": [], "difficulty": 6, - "topics": ["lists", "maps", "transforming"] + "topics": [ + "lists", + "maps", + "transforming" + ] }, { "slug": "grade-school", @@ -856,7 +1068,13 @@ "practices": [], "prerequisites": [], "difficulty": 6, - "topics": ["conditionals", "lists", "maps", "sorting", "strings"] + "topics": [ + "conditionals", + "lists", + "maps", + "sorting", + "strings" + ] }, { "slug": "affine-cipher", @@ -865,7 +1083,11 @@ "practices": [], "prerequisites": [], "difficulty": 4, - "topics": ["cryptography", "security", "strings"] + "topics": [ + "cryptography", + "security", + "strings" + ] }, { "slug": "atbash-cipher", @@ -874,7 +1096,11 @@ "practices": [], "prerequisites": [], "difficulty": 4, - "topics": ["cryptography", "security", "strings"] + "topics": [ + "cryptography", + "security", + "strings" + ] }, { "slug": "rail-fence-cipher", @@ -883,7 +1109,12 @@ "practices": [], "prerequisites": [], "difficulty": 6, - "topics": ["cryptography", "algorithms", "strings", "transforming"] + "topics": [ + "cryptography", + "algorithms", + "strings", + "transforming" + ] }, { "slug": "say", @@ -892,7 +1123,10 @@ "practices": [], "prerequisites": [], "difficulty": 6, - "topics": ["strings", "transforming"] + "topics": [ + "strings", + "transforming" + ] }, { "slug": "simple-cipher", @@ -916,7 +1150,13 @@ "practices": [], "prerequisites": [], "difficulty": 7, - "topics": ["enumerations", "generics", "lists", "loops", "searching"] + "topics": [ + "enumerations", + "generics", + "lists", + "loops", + "searching" + ] }, { "slug": "diffie-hellman", @@ -925,7 +1165,12 @@ "practices": [], "prerequisites": [], "difficulty": 8, - "topics": ["cryptography", "randomness", "algorithms", "integers"] + "topics": [ + "cryptography", + "randomness", + "algorithms", + "integers" + ] }, { "slug": "list-ops", @@ -949,7 +1194,12 @@ "practices": [], "prerequisites": [], "difficulty": 9, - "topics": ["algorithms", "arrays", "conditionals", "games"] + "topics": [ + "algorithms", + "arrays", + "conditionals", + "games" + ] }, { "slug": "react", @@ -972,8 +1222,10 @@ "practices": [], "prerequisites": [], "difficulty": 1, - "topics": ["lists"], - "status": "deprecated" + "status": "deprecated", + "topics": [ + "lists" + ] }, { "slug": "binary", @@ -982,8 +1234,10 @@ "practices": [], "prerequisites": [], "difficulty": 1, - "topics": ["strings"], - "status": "deprecated" + "status": "deprecated", + "topics": [ + "strings" + ] }, { "slug": "hexadecimal", @@ -992,8 +1246,11 @@ "practices": [], "prerequisites": [], "difficulty": 1, - "topics": ["string", "integers"], - "status": "deprecated" + "status": "deprecated", + "topics": [ + "string", + "integers" + ] }, { "slug": "strain", @@ -1002,8 +1259,11 @@ "practices": [], "prerequisites": [], "difficulty": 1, - "topics": ["arrays", "filtering"], - "status": "deprecated" + "status": "deprecated", + "topics": [ + "arrays", + "filtering" + ] }, { "slug": "dnd-character", @@ -1012,7 +1272,10 @@ "practices": [], "prerequisites": [], "difficulty": 1, - "topics": ["integers", "randomness"] + "topics": [ + "integers", + "randomness" + ] }, { "slug": "reverse-string", @@ -1021,7 +1284,9 @@ "practices": [], "prerequisites": [], "difficulty": 1, - "topics": ["strings"] + "topics": [ + "strings" + ] }, { "slug": "darts", @@ -1030,16 +1295,38 @@ "practices": [], "prerequisites": [], "difficulty": 2, - "topics": ["pattern_matching", "conditionals", "math"] + "topics": [ + "pattern_matching", + "conditionals", + "math" + ] }, { "slug": "knapsack", "name": "Knapsack", "uuid": "1c2e8e65-1d1f-43ce-a626-70769f8bae5f", "practices": [], - "prerequisites": ["arrays", "loops", "lists", "conditionals"], + "prerequisites": [ + "arrays", + "loops", + "lists", + "conditionals" + ], "difficulty": 5, - "topics": ["algorithms", "logic"] + "topics": [ + "algorithms", + "logic" + ] + }, + { + "slug": "circular-buffer", + "name": "Circular Buffer", + "uuid": "e32c0edd-367c-4b66-b677-35e518d6831f", + "practices": [], + "prerequisites": [ + "generics" + ], + "difficulty": 3 } ] }, @@ -1057,52 +1344,52 @@ ], "key_features": [ { - "icon": "powerful", "title": "Pragmatic", - "content": "Kotlin is trying hard to be a modern language with great tooling support." + "content": "Kotlin is trying hard to be a modern language with great tooling support.", + "icon": "powerful" }, { - "icon": "immutable", "title": "Statically Typed", - "content": "Strong type-safety and null-safety features for building robust software." + "content": "Strong type-safety and null-safety features for building robust software.", + "icon": "immutable" }, { - "icon": "multi-paradigm", "title": "Multi-Paradigm", - "content": "Kotlin support both functional and mainstream OOP programming principles." + "content": "Kotlin support both functional and mainstream OOP programming principles.", + "icon": "multi-paradigm" }, { - "icon": "cross-platform", "title": "Platform Independent", - "content": "Develop for Android, iOS, JVM, Node.js, browser and many native targets." + "content": "Develop for Android, iOS, JVM, Node.js, browser and many native targets.", + "icon": "cross-platform" }, { - "icon": "garbage-collected", "title": "Garbage collected", - "content": "Kotlin (as a modern generic-purpose language) does memory management for you." + "content": "Kotlin (as a modern generic-purpose language) does memory management for you.", + "icon": "garbage-collected" }, { - "icon": "community", "title": "Open-Source", - "content": "Kotlin is fully open-source. Submit patches or suggest language improvements." + "content": "Kotlin is fully open-source. Submit patches or suggest language improvements.", + "icon": "community" } ], "tags": [ + "execution_mode/compiled", "paradigm/functional", "paradigm/imperative", "paradigm/object_oriented", - "typing/static", - "typing/strong", - "execution_mode/compiled", - "platform/windows", - "platform/mac", - "platform/linux", "platform/android", + "platform/linux", + "platform/mac", "platform/web", + "platform/windows", "runtime/jvm", + "typing/static", + "typing/strong", "used_for/backends", "used_for/cross_platform_development", - "used_for/mobile", - "used_for/games" + "used_for/games", + "used_for/mobile" ] } diff --git a/exercises/practice/circular-buffer/.docs/instructions.md b/exercises/practice/circular-buffer/.docs/instructions.md new file mode 100644 index 00000000..2ba1fda2 --- /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 00000000..79f397b2 --- /dev/null +++ b/exercises/practice/circular-buffer/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "kahgoh" + ], + "files": { + "solution": [ + "src/main/kotlin/CircularBuffer.kt" + ], + "test": [ + "src/test/kotlin/CircularBufferTest.kt" + ], + "example": [ + ".meta/src/reference/kotlin/CircularBuffer.kt" + ] + }, + "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/src/reference/kotlin/CircularBuffer.kt b/exercises/practice/circular-buffer/.meta/src/reference/kotlin/CircularBuffer.kt new file mode 100644 index 00000000..251541a5 --- /dev/null +++ b/exercises/practice/circular-buffer/.meta/src/reference/kotlin/CircularBuffer.kt @@ -0,0 +1,36 @@ +import kotlin.collections.ArrayDeque + +class EmptyBufferException : Exception("Buffer is empty") {} + +class BufferFullException : Exception("Buffer is full") {} + +class CircularBuffer(private val capacity: Int) { + + private val contents = ArrayDeque(capacity) + + fun read() : T { + if (contents.isNotEmpty()) { + return contents.removeFirst() + } + throw EmptyBufferException() + } + + fun write(value: T) { + if (contents.size < capacity) { + contents.add(value) + } else { + throw BufferFullException() + } + } + + fun overwrite(value: T) { + if (contents.size >= capacity) { + contents.removeFirst() + } + contents.add(value) + } + + fun clear() { + contents.clear() + } +} \ 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 00000000..0fb3143d --- /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/build.gradle.kts b/exercises/practice/circular-buffer/build.gradle.kts new file mode 100644 index 00000000..3db5dc4f --- /dev/null +++ b/exercises/practice/circular-buffer/build.gradle.kts @@ -0,0 +1,23 @@ +import org.gradle.api.tasks.testing.logging.TestExceptionFormat + +plugins { + kotlin("jvm") +} + +repositories { + mavenCentral() +} + +dependencies { + implementation(kotlin("stdlib-jdk8")) + + testImplementation("junit:junit:4.13.2") + testImplementation(kotlin("test-junit")) +} + +tasks.withType { + testLogging { + exceptionFormat = TestExceptionFormat.FULL + events("passed", "failed", "skipped") + } +} diff --git a/exercises/practice/circular-buffer/gradle/wrapper/gradle-wrapper.jar b/exercises/practice/circular-buffer/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..7f93135c Binary files /dev/null and b/exercises/practice/circular-buffer/gradle/wrapper/gradle-wrapper.jar differ diff --git a/exercises/practice/circular-buffer/gradle/wrapper/gradle-wrapper.properties b/exercises/practice/circular-buffer/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..3fa8f862 --- /dev/null +++ b/exercises/practice/circular-buffer/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/exercises/practice/circular-buffer/gradlew b/exercises/practice/circular-buffer/gradlew new file mode 100755 index 00000000..1aa94a42 --- /dev/null +++ b/exercises/practice/circular-buffer/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/exercises/practice/circular-buffer/gradlew.bat b/exercises/practice/circular-buffer/gradlew.bat new file mode 100755 index 00000000..93e3f59f --- /dev/null +++ b/exercises/practice/circular-buffer/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/exercises/practice/circular-buffer/settings.gradle.kts b/exercises/practice/circular-buffer/settings.gradle.kts new file mode 100644 index 00000000..054e2f7e --- /dev/null +++ b/exercises/practice/circular-buffer/settings.gradle.kts @@ -0,0 +1,13 @@ +pluginManagement { + repositories { + mavenCentral() + gradlePluginPortal() + } + resolutionStrategy { + eachPlugin { + when (requested.id.id) { + "org.jetbrains.kotlin.jvm" -> useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.0") + } + } + } +} diff --git a/exercises/practice/circular-buffer/src/main/kotlin/CircularBuffer.kt b/exercises/practice/circular-buffer/src/main/kotlin/CircularBuffer.kt new file mode 100644 index 00000000..241cdd1a --- /dev/null +++ b/exercises/practice/circular-buffer/src/main/kotlin/CircularBuffer.kt @@ -0,0 +1,27 @@ +import kotlin.collections.ArrayDeque + +// TODO: implement proper exceptions to complete the task +class EmptyBufferException + +class BufferFullException + +class CircularBuffer { + // TODO: implement proper constructor to complete the task + + fun read() : T { + TODO("Implement this function to complete the task") + } + + fun write(value: T) { + TODO("Implement this function to complete the task") + + } + + fun overwrite(value: T) { + TODO("Implement this function to complete the task") + } + + fun clear() { + TODO("Implement this function to complete the task") + } +} \ No newline at end of file diff --git a/exercises/practice/circular-buffer/src/test/kotlin/CircularBufferTest.kt b/exercises/practice/circular-buffer/src/test/kotlin/CircularBufferTest.kt new file mode 100644 index 00000000..78d3541e --- /dev/null +++ b/exercises/practice/circular-buffer/src/test/kotlin/CircularBufferTest.kt @@ -0,0 +1,169 @@ +import org.junit.Test +import org.junit.Ignore +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith + +class CircularBufferTest { + @Test + fun `reading empty buffer should fail`() { + val buffer = CircularBuffer(1) + assertFailsWith() { + buffer.read() + } + } + + @Ignore + @Test + fun `can read an item just written`() { + val buffer = CircularBuffer(1) + buffer.write(1) + assertEquals(1, buffer.read()) + } + + @Ignore + @Test + fun `each item may only be read once`() { + val buffer = CircularBuffer(1) + buffer.write(1) + + assertEquals(1, buffer.read()) + assertFailsWith { + buffer.read() + } + } + + @Ignore + @Test + fun `items are read in the order they are written`() { + val buffer = CircularBuffer(2) + buffer.write(1) + buffer.write(2) + + assertEquals(1, buffer.read()) + assertEquals(2, buffer.read()) + } + + @Ignore + @Test + fun `full buffer can't be written to`() { + val buffer = CircularBuffer(1) + buffer.write(1) + assertFailsWith { + buffer.write(2) + } + } + + @Ignore + @Test + fun `a read frees up capacity for another write`() { + val buffer = CircularBuffer(1) + buffer.write(1) + assertEquals(1, buffer.read()) + + buffer.write(2) + assertEquals(2, buffer.read()) + } + + @Ignore + @Test + fun `read position is maintained even across multiple writes`() { + val buffer = CircularBuffer(3) + buffer.write(1) + buffer.write(2) + assertEquals(1, buffer.read()) + + buffer.write(3) + assertEquals(2, buffer.read()) + assertEquals(3, buffer.read()) + } + + @Ignore + @Test + fun `items cleared out of buffer can't be read`() { + val buffer = CircularBuffer(1) + buffer.write(1) + buffer.clear() + + assertFailsWith { + buffer.read() + } + } + + @Ignore + @Test + fun `clear frees up capacity for another write`() { + val buffer = CircularBuffer(1) + buffer.write(1) + buffer.clear() + buffer.write(2) + + assertEquals(2, buffer.read()) + } + + @Ignore + @Test + fun `clear does nothing on empty buffer`() { + val buffer = CircularBuffer(1) + buffer.clear() + buffer.write(1) + + assertEquals(1, buffer.read()) + } + + @Ignore + @Test + fun `overwrite acts like write on non-full buffer`() { + val buffer = CircularBuffer(2) + buffer.write(1) + buffer.overwrite(2) + + assertEquals(1, buffer.read()) + assertEquals(2, buffer.read()) + } + + @Ignore + @Test + fun `overwrite replaces the oldest item on full buffer`() { + val buffer = CircularBuffer(2) + buffer.write(1) + buffer.write(2) + buffer.overwrite(3) + + assertEquals(2, buffer.read()) + assertEquals(3, buffer.read()) + } + + @Ignore + @Test + fun `overwrite replaces the oldest item remaining in buffer following a read`() { + val buffer = CircularBuffer(3) + buffer.write(1) + buffer.write(2) + buffer.write(3) + + assertEquals(1, buffer.read()) + buffer.write(4) + buffer.overwrite(5) + + assertEquals(3, buffer.read()) + assertEquals(4, buffer.read()) + assertEquals(5, buffer.read()) + } + + @Ignore + @Test + fun `initial clear does not affect wrapping around`() { + val buffer = CircularBuffer(2) + buffer.clear() + buffer.write(1) + buffer.write(2) + buffer.overwrite(3) + buffer.overwrite(4) + + assertEquals(3, buffer.read()) + assertEquals(4, buffer.read()) + assertFailsWith { + buffer.read() + } + } +} diff --git a/exercises/settings.gradle.kts b/exercises/settings.gradle.kts index 0c1255e2..467011ed 100644 --- a/exercises/settings.gradle.kts +++ b/exercises/settings.gradle.kts @@ -15,6 +15,7 @@ include( "practice:bob", "practice:bowling", "practice:change", + "practice:circular-buffer", "practice:clock", "practice:collatz-conjecture", "practice:complex-numbers",