From cde1633c213af614b7b2c3f85d5277d5308f7ce3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Thu, 10 Oct 2024 18:44:35 +1300 Subject: [PATCH 1/2] Add run-length-encoding exercise --- config.json | 8 ++ .../run-length-encoding/.docs/instructions.md | 20 +++ .../run-length-encoding/.meta/Example.roc | 44 +++++++ .../run-length-encoding/.meta/config.json | 19 +++ .../run-length-encoding/.meta/template.j2 | 27 +++++ .../run-length-encoding/.meta/tests.toml | 49 ++++++++ .../run-length-encoding/RunLengthEncoding.roc | 9 ++ .../run-length-encoding-test.roc | 114 ++++++++++++++++++ 8 files changed, 290 insertions(+) create mode 100644 exercises/practice/run-length-encoding/.docs/instructions.md create mode 100644 exercises/practice/run-length-encoding/.meta/Example.roc create mode 100644 exercises/practice/run-length-encoding/.meta/config.json create mode 100644 exercises/practice/run-length-encoding/.meta/template.j2 create mode 100644 exercises/practice/run-length-encoding/.meta/tests.toml create mode 100644 exercises/practice/run-length-encoding/RunLengthEncoding.roc create mode 100644 exercises/practice/run-length-encoding/run-length-encoding-test.roc diff --git a/config.json b/config.json index d0dd9e4..a361e25 100644 --- a/config.json +++ b/config.json @@ -563,6 +563,14 @@ "prerequisites": [], "difficulty": 4 }, + { + "slug": "run-length-encoding", + "name": "Run-Length Encoding", + "uuid": "d6b8a979-8ce8-4f46-a6cd-35cd44b19701", + "practices": [], + "prerequisites": [], + "difficulty": 4 + }, { "slug": "all-your-base", "name": "All Your Base", diff --git a/exercises/practice/run-length-encoding/.docs/instructions.md b/exercises/practice/run-length-encoding/.docs/instructions.md new file mode 100644 index 0000000..fc8ce05 --- /dev/null +++ b/exercises/practice/run-length-encoding/.docs/instructions.md @@ -0,0 +1,20 @@ +# Instructions + +Implement run-length encoding and decoding. + +Run-length encoding (RLE) is a simple form of data compression, where runs (consecutive data elements) are replaced by just one data value and count. + +For example we can represent the original 53 characters with only 13. + +```text +"WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB" -> "12WB12W3B24WB" +``` + +RLE allows the original data to be perfectly reconstructed from the compressed data, which makes it a lossless data compression. + +```text +"AABCCCDEEEE" -> "2AB3CD4E" -> "AABCCCDEEEE" +``` + +For simplicity, you can assume that the unencoded string will only contain the letters A through Z (either lower or upper case) and whitespace. +This way data to be encoded will never contain any numbers and numbers inside data to be decoded always represent the count for the following character. diff --git a/exercises/practice/run-length-encoding/.meta/Example.roc b/exercises/practice/run-length-encoding/.meta/Example.roc new file mode 100644 index 0000000..86b920c --- /dev/null +++ b/exercises/practice/run-length-encoding/.meta/Example.roc @@ -0,0 +1,44 @@ +module [encode, decode] + +encode : Str -> Result Str [BadUtf8 _ _] +encode = \string -> + appendCountAndLetter = \state -> + if state.count == 0 then + [] + else if state.count == 1 then + state.chars |> List.append state.lastChar + else + digits = state.count |> Num.toStr |> Str.toUtf8 + state.chars |> List.concat digits |> List.append state.lastChar + + string + |> Str.toUtf8 + |> List.walk { chars: [], lastChar: 0, count: 0 } \state, char -> + if state.count == 0 then + { chars: [], lastChar: char, count: 1 } + else if state.lastChar == char then + { state & count: state.count + 1 } + else + chars = appendCountAndLetter state + { chars, lastChar: char, count: 1 } + |> \state -> appendCountAndLetter state + |> Str.fromUtf8 + +decode : Str -> Result Str [BadUtf8 _ _, InvalidNumStr] +decode = \string -> + string + |> Str.toUtf8 + |> List.walkTry? { chars: [], digits: [] } \state, char -> + if char >= '0' && char <= '9' then + digits = state.digits |> List.append char + Ok { state & digits } + else if state.digits == [] then + chars = state.chars |> List.append char + Ok { state & chars } + else + countStr = Str.fromUtf8? state.digits + count = Str.toU64? countStr + chars = state.chars |> List.concat (List.repeat char count) + Ok { chars, digits: [] } + |> .chars + |> Str.fromUtf8 diff --git a/exercises/practice/run-length-encoding/.meta/config.json b/exercises/practice/run-length-encoding/.meta/config.json new file mode 100644 index 0000000..d5120df --- /dev/null +++ b/exercises/practice/run-length-encoding/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "ageron" + ], + "files": { + "solution": [ + "RunLengthEncoding.roc" + ], + "test": [ + "run-length-encoding-test.roc" + ], + "example": [ + ".meta/Example.roc" + ] + }, + "blurb": "Implement run-length encoding and decoding.", + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Run-length_encoding" +} diff --git a/exercises/practice/run-length-encoding/.meta/template.j2 b/exercises/practice/run-length-encoding/.meta/template.j2 new file mode 100644 index 0000000..104278d --- /dev/null +++ b/exercises/practice/run-length-encoding/.meta/template.j2 @@ -0,0 +1,27 @@ +{%- import "generator_macros.j2" as macros with context -%} +{{ macros.canonical_ref() }} +{{ macros.header() }} + +import {{ exercise | to_pascal }} exposing [encode, decode] + +{% for supercase in cases %} +## +## {{ supercase["description"] }} +## + +{% for case in supercase["cases"] -%} +# {{ case["description"] }} +expect + string = {{ case["input"]["string"] | to_roc }} + {%- if case["property"] == "consistency" %} + result = string |> encode |> Result.try decode + result == Ok string +{%- else %} + result = string |> {{ case["property"] | to_camel }} + expected = {{ case["expected"] | to_roc }} + result == Ok expected +{%- endif %} + +{% endfor %} +{% endfor %} + diff --git a/exercises/practice/run-length-encoding/.meta/tests.toml b/exercises/practice/run-length-encoding/.meta/tests.toml new file mode 100644 index 0000000..7bdb808 --- /dev/null +++ b/exercises/practice/run-length-encoding/.meta/tests.toml @@ -0,0 +1,49 @@ +# 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. + +[ad53b61b-6ffc-422f-81a6-61f7df92a231] +description = "run-length encode a string -> empty string" + +[52012823-b7e6-4277-893c-5b96d42f82de] +description = "run-length encode a string -> single characters only are encoded without count" + +[b7868492-7e3a-415f-8da3-d88f51f80409] +description = "run-length encode a string -> string with no single characters" + +[859b822b-6e9f-44d6-9c46-6091ee6ae358] +description = "run-length encode a string -> single characters mixed with repeated characters" + +[1b34de62-e152-47be-bc88-469746df63b3] +description = "run-length encode a string -> multiple whitespace mixed in string" + +[abf176e2-3fbd-40ad-bb2f-2dd6d4df721a] +description = "run-length encode a string -> lowercase characters" + +[7ec5c390-f03c-4acf-ac29-5f65861cdeb5] +description = "run-length decode a string -> empty string" + +[ad23f455-1ac2-4b0e-87d0-b85b10696098] +description = "run-length decode a string -> single characters only" + +[21e37583-5a20-4a0e-826c-3dee2c375f54] +description = "run-length decode a string -> string with no single characters" + +[1389ad09-c3a8-4813-9324-99363fba429c] +description = "run-length decode a string -> single characters with repeated characters" + +[3f8e3c51-6aca-4670-b86c-a213bf4706b0] +description = "run-length decode a string -> multiple whitespace mixed in string" + +[29f721de-9aad-435f-ba37-7662df4fb551] +description = "run-length decode a string -> lowercase string" + +[2a762efd-8695-4e04-b0d6-9736899fbc16] +description = "encode and then decode -> encode followed by decode gives original string" diff --git a/exercises/practice/run-length-encoding/RunLengthEncoding.roc b/exercises/practice/run-length-encoding/RunLengthEncoding.roc new file mode 100644 index 0000000..5c79319 --- /dev/null +++ b/exercises/practice/run-length-encoding/RunLengthEncoding.roc @@ -0,0 +1,9 @@ +module [encode, decode] + +encode : Str -> Result Str _ +encode = \string -> + crash "Please implement the 'encode' function" + +decode : Str -> Result Str _ +decode = \string -> + crash "Please implement the 'decode' function" diff --git a/exercises/practice/run-length-encoding/run-length-encoding-test.roc b/exercises/practice/run-length-encoding/run-length-encoding-test.roc new file mode 100644 index 0000000..08f5d39 --- /dev/null +++ b/exercises/practice/run-length-encoding/run-length-encoding-test.roc @@ -0,0 +1,114 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/run-length-encoding/canonical-data.json +# File last updated on 2024-10-10 +app [main] { + pf: platform "https://github.com/roc-lang/basic-cli/releases/download/0.15.0/SlwdbJ-3GR7uBWQo6zlmYWNYOxnvo8r6YABXD-45UOw.tar.br", +} + +main = + Task.ok {} + +import RunLengthEncoding exposing [encode, decode] + +## +## run-length encode a string +## + +# empty string +expect + string = "" + result = string |> encode + expected = "" + result == Ok expected + +# single characters only are encoded without count +expect + string = "XYZ" + result = string |> encode + expected = "XYZ" + result == Ok expected + +# string with no single characters +expect + string = "AABBBCCCC" + result = string |> encode + expected = "2A3B4C" + result == Ok expected + +# single characters mixed with repeated characters +expect + string = "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB" + result = string |> encode + expected = "12WB12W3B24WB" + result == Ok expected + +# multiple whitespace mixed in string +expect + string = " hsqq qww " + result = string |> encode + expected = "2 hs2q q2w2 " + result == Ok expected + +# lowercase characters +expect + string = "aabbbcccc" + result = string |> encode + expected = "2a3b4c" + result == Ok expected + +## +## run-length decode a string +## + +# empty string +expect + string = "" + result = string |> decode + expected = "" + result == Ok expected + +# single characters only +expect + string = "XYZ" + result = string |> decode + expected = "XYZ" + result == Ok expected + +# string with no single characters +expect + string = "2A3B4C" + result = string |> decode + expected = "AABBBCCCC" + result == Ok expected + +# single characters with repeated characters +expect + string = "12WB12W3B24WB" + result = string |> decode + expected = "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWB" + result == Ok expected + +# multiple whitespace mixed in string +expect + string = "2 hs2q q2w2 " + result = string |> decode + expected = " hsqq qww " + result == Ok expected + +# lowercase string +expect + string = "2a3b4c" + result = string |> decode + expected = "aabbbcccc" + result == Ok expected + +## +## encode and then decode +## + +# encode followed by decode gives original string +expect + string = "zzz ZZ zZ" + result = string |> encode |> Result.try decode + result == Ok string + From 778fd26e3cdcb5d2d4e1e78911b51abb653a0417 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Geron?= Date: Fri, 11 Oct 2024 14:22:29 +1300 Subject: [PATCH 2/2] Update exercises/practice/run-length-encoding/.meta/Example.roc Replace `|> \state -> appendCountAndLetter state` with `|> appendCountAndLetter` as suggested by Isaac. Co-authored-by: Isaac Van Doren <69181572+isaacvando@users.noreply.github.com> --- exercises/practice/run-length-encoding/.meta/Example.roc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/practice/run-length-encoding/.meta/Example.roc b/exercises/practice/run-length-encoding/.meta/Example.roc index 86b920c..8e23de4 100644 --- a/exercises/practice/run-length-encoding/.meta/Example.roc +++ b/exercises/practice/run-length-encoding/.meta/Example.roc @@ -21,7 +21,7 @@ encode = \string -> else chars = appendCountAndLetter state { chars, lastChar: char, count: 1 } - |> \state -> appendCountAndLetter state + |> appendCountAndLetter |> Str.fromUtf8 decode : Str -> Result Str [BadUtf8 _ _, InvalidNumStr]