diff --git a/config.json b/config.json index 696caff..93ed827 100644 --- a/config.json +++ b/config.json @@ -595,6 +595,14 @@ "prerequisites": [], "difficulty": 4 }, + { + "slug": "variable-length-quantity", + "name": "Variable Length Quantity", + "uuid": "3d6cb550-b77f-44f3-a617-1243a4a52dcb", + "practices": [], + "prerequisites": [], + "difficulty": 4 + }, { "slug": "all-your-base", "name": "All Your Base", diff --git a/exercises/practice/variable-length-quantity/.docs/instructions.md b/exercises/practice/variable-length-quantity/.docs/instructions.md new file mode 100644 index 0000000..5012548 --- /dev/null +++ b/exercises/practice/variable-length-quantity/.docs/instructions.md @@ -0,0 +1,34 @@ +# Instructions + +Implement variable length quantity encoding and decoding. + +The goal of this exercise is to implement [VLQ][vlq] encoding/decoding. + +In short, the goal of this encoding is to encode integer values in a way that would save bytes. +Only the first 7 bits of each byte are significant (right-justified; sort of like an ASCII byte). +So, if you have a 32-bit value, you have to unpack it into a series of 7-bit bytes. +Of course, you will have a variable number of bytes depending upon your integer. +To indicate which is the last byte of the series, you leave bit #7 clear. +In all of the preceding bytes, you set bit #7. + +So, if an integer is between `0-127`, it can be represented as one byte. +Although VLQ can deal with numbers of arbitrary sizes, for this exercise we will restrict ourselves to only numbers that fit in a 32-bit unsigned integer. +Here are examples of integers as 32-bit values, and the variable length quantities that they translate to: + +```text + NUMBER VARIABLE QUANTITY +00000000 00 +00000040 40 +0000007F 7F +00000080 81 00 +00002000 C0 00 +00003FFF FF 7F +00004000 81 80 00 +00100000 C0 80 00 +001FFFFF FF FF 7F +00200000 81 80 80 00 +08000000 C0 80 80 00 +0FFFFFFF FF FF FF 7F +``` + +[vlq]: https://en.wikipedia.org/wiki/Variable-length_quantity diff --git a/exercises/practice/variable-length-quantity/.meta/Example.roc b/exercises/practice/variable-length-quantity/.meta/Example.roc new file mode 100644 index 0000000..c8250c9 --- /dev/null +++ b/exercises/practice/variable-length-quantity/.meta/Example.roc @@ -0,0 +1,40 @@ +module [encode, decode] + +encode : List U32 -> List U8 +encode = \integers -> + integers |> List.joinMap encodeInteger + +encodeInteger : U32 -> List U8 +encodeInteger = \integer -> + help = \bytes, n -> + if n == 0 then + bytes + else + nextN = n // 128 # same as n |> Num.shiftRightZfBy 7 + last7Bits = n % 128 |> Num.toU8 # same as n |> Num.bitwiseAnd 0b1111111 |> Num.toU8 + byte = + if bytes == [] then + last7Bits + else + last7Bits + 128 # same as last7Bits |> Num.bitwiseOr 0b10000000 + help (bytes |> List.append byte) nextN + + if integer == 0 then [0] else help [] integer |> List.reverse + +decode : List U8 -> Result (List U32) [IncompleteSequence] +decode = \bytes -> + when bytes is + [] -> Err IncompleteSequence + [.., last] if last >= 128 -> Err IncompleteSequence + _ -> + bytes + |> List.walk { integers: [], integer: 0 } \state, byte -> + last7Bits = byte % 128 + integer = state.integer * 128 + Num.toU32 last7Bits + if byte >= 128 then + { state & integer } + else + { integers: state.integers |> List.append integer, integer: 0 } + |> .integers + |> Ok + diff --git a/exercises/practice/variable-length-quantity/.meta/config.json b/exercises/practice/variable-length-quantity/.meta/config.json new file mode 100644 index 0000000..ab6a65a --- /dev/null +++ b/exercises/practice/variable-length-quantity/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "ageron" + ], + "files": { + "solution": [ + "VariableLengthQuantity.roc" + ], + "test": [ + "variable-length-quantity-test.roc" + ], + "example": [ + ".meta/Example.roc" + ] + }, + "blurb": "Implement variable length quantity encoding and decoding.", + "source": "A poor Splice developer having to implement MIDI encoding/decoding.", + "source_url": "https://splice.com" +} diff --git a/exercises/practice/variable-length-quantity/.meta/template.j2 b/exercises/practice/variable-length-quantity/.meta/template.j2 new file mode 100644 index 0000000..dc0c8a0 --- /dev/null +++ b/exercises/practice/variable-length-quantity/.meta/template.j2 @@ -0,0 +1,33 @@ +{%- 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"] }} +{%- if case["property"] == "encode" %} +expect + integers = {{ case["input"]["integers"] | to_roc }} + result = encode integers + expected = {{ case["expected"] | to_roc }} + result == expected +{%- else %} +expect + bytes = {{ case["input"]["integers"] | to_roc }} + result = decode bytes + {%- if case["expected"]["error"] %} + result |> Result.isErr + {%- else %} + expected = Ok {{ case["expected"] | to_roc }} + result == expected + {%- endif %} +{%- endif %} + +{% endfor %} +{% endfor %} diff --git a/exercises/practice/variable-length-quantity/.meta/tests.toml b/exercises/practice/variable-length-quantity/.meta/tests.toml new file mode 100644 index 0000000..c9af549 --- /dev/null +++ b/exercises/practice/variable-length-quantity/.meta/tests.toml @@ -0,0 +1,88 @@ +# 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. + +[35c9db2e-f781-4c52-b73b-8e76427defd0] +description = "Encode a series of integers, producing a series of bytes. -> zero" + +[be44d299-a151-4604-a10e-d4b867f41540] +description = "Encode a series of integers, producing a series of bytes. -> arbitrary single byte" + +[ea399615-d274-4af6-bbef-a1c23c9e1346] +description = "Encode a series of integers, producing a series of bytes. -> largest single byte" + +[77b07086-bd3f-4882-8476-8dcafee79b1c] +description = "Encode a series of integers, producing a series of bytes. -> smallest double byte" + +[63955a49-2690-4e22-a556-0040648d6b2d] +description = "Encode a series of integers, producing a series of bytes. -> arbitrary double byte" + +[29da7031-0067-43d3-83a7-4f14b29ed97a] +description = "Encode a series of integers, producing a series of bytes. -> largest double byte" + +[3345d2e3-79a9-4999-869e-d4856e3a8e01] +description = "Encode a series of integers, producing a series of bytes. -> smallest triple byte" + +[5df0bc2d-2a57-4300-a653-a75ee4bd0bee] +description = "Encode a series of integers, producing a series of bytes. -> arbitrary triple byte" + +[f51d8539-312d-4db1-945c-250222c6aa22] +description = "Encode a series of integers, producing a series of bytes. -> largest triple byte" + +[da78228b-544f-47b7-8bfe-d16b35bbe570] +description = "Encode a series of integers, producing a series of bytes. -> smallest quadruple byte" + +[11ed3469-a933-46f1-996f-2231e05d7bb6] +description = "Encode a series of integers, producing a series of bytes. -> arbitrary quadruple byte" + +[d5f3f3c3-e0f1-4e7f-aad0-18a44f223d1c] +description = "Encode a series of integers, producing a series of bytes. -> largest quadruple byte" + +[91a18b33-24e7-4bfb-bbca-eca78ff4fc47] +description = "Encode a series of integers, producing a series of bytes. -> smallest quintuple byte" + +[5f34ff12-2952-4669-95fe-2d11b693d331] +description = "Encode a series of integers, producing a series of bytes. -> arbitrary quintuple byte" + +[7489694b-88c3-4078-9864-6fe802411009] +description = "Encode a series of integers, producing a series of bytes. -> maximum 32-bit integer input" + +[f9b91821-cada-4a73-9421-3c81d6ff3661] +description = "Encode a series of integers, producing a series of bytes. -> two single-byte values" + +[68694449-25d2-4974-ba75-fa7bb36db212] +description = "Encode a series of integers, producing a series of bytes. -> two multi-byte values" + +[51a06b5c-de1b-4487-9a50-9db1b8930d85] +description = "Encode a series of integers, producing a series of bytes. -> many multi-byte values" + +[baa73993-4514-4915-bac0-f7f585e0e59a] +description = "Decode a series of bytes, producing a series of integers. -> one byte" + +[72e94369-29f9-46f2-8c95-6c5b7a595aee] +description = "Decode a series of bytes, producing a series of integers. -> two bytes" + +[df5a44c4-56f7-464e-a997-1db5f63ce691] +description = "Decode a series of bytes, producing a series of integers. -> three bytes" + +[1bb58684-f2dc-450a-8406-1f3452aa1947] +description = "Decode a series of bytes, producing a series of integers. -> four bytes" + +[cecd5233-49f1-4dd1-a41a-9840a40f09cd] +description = "Decode a series of bytes, producing a series of integers. -> maximum 32-bit integer" + +[e7d74ba3-8b8e-4bcb-858d-d08302e15695] +description = "Decode a series of bytes, producing a series of integers. -> incomplete sequence causes error" + +[aa378291-9043-4724-bc53-aca1b4a3fcb6] +description = "Decode a series of bytes, producing a series of integers. -> incomplete sequence causes error, even if value is zero" + +[a91e6f5a-c64a-48e3-8a75-ce1a81e0ebee] +description = "Decode a series of bytes, producing a series of integers. -> multiple values" diff --git a/exercises/practice/variable-length-quantity/VariableLengthQuantity.roc b/exercises/practice/variable-length-quantity/VariableLengthQuantity.roc new file mode 100644 index 0000000..38a2f36 --- /dev/null +++ b/exercises/practice/variable-length-quantity/VariableLengthQuantity.roc @@ -0,0 +1,9 @@ +module [encode, decode] + +encode : List U32 -> List U8 +encode = \integers -> + crash "Please implement the 'encode' function" + +decode : List U8 -> Result (List U32) _ +decode = \bytes -> + crash "Please implement the 'decode' function" diff --git a/exercises/practice/variable-length-quantity/variable-length-quantity-test.roc b/exercises/practice/variable-length-quantity/variable-length-quantity-test.roc new file mode 100644 index 0000000..aa15b69 --- /dev/null +++ b/exercises/practice/variable-length-quantity/variable-length-quantity-test.roc @@ -0,0 +1,200 @@ +# These tests are auto-generated with test data from: +# https://github.com/exercism/problem-specifications/tree/main/exercises/variable-length-quantity/canonical-data.json +# File last updated on 2024-10-15 +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 VariableLengthQuantity exposing [encode, decode] + +## +## Encode a series of integers, producing a series of bytes. +## + +# zero +expect + integers = [0] + result = encode integers + expected = [0] + result == expected + +# arbitrary single byte +expect + integers = [64] + result = encode integers + expected = [64] + result == expected + +# largest single byte +expect + integers = [127] + result = encode integers + expected = [127] + result == expected + +# smallest double byte +expect + integers = [128] + result = encode integers + expected = [129, 0] + result == expected + +# arbitrary double byte +expect + integers = [8192] + result = encode integers + expected = [192, 0] + result == expected + +# largest double byte +expect + integers = [16383] + result = encode integers + expected = [255, 127] + result == expected + +# smallest triple byte +expect + integers = [16384] + result = encode integers + expected = [129, 128, 0] + result == expected + +# arbitrary triple byte +expect + integers = [1048576] + result = encode integers + expected = [192, 128, 0] + result == expected + +# largest triple byte +expect + integers = [2097151] + result = encode integers + expected = [255, 255, 127] + result == expected + +# smallest quadruple byte +expect + integers = [2097152] + result = encode integers + expected = [129, 128, 128, 0] + result == expected + +# arbitrary quadruple byte +expect + integers = [134217728] + result = encode integers + expected = [192, 128, 128, 0] + result == expected + +# largest quadruple byte +expect + integers = [268435455] + result = encode integers + expected = [255, 255, 255, 127] + result == expected + +# smallest quintuple byte +expect + integers = [268435456] + result = encode integers + expected = [129, 128, 128, 128, 0] + result == expected + +# arbitrary quintuple byte +expect + integers = [4278190080] + result = encode integers + expected = [143, 248, 128, 128, 0] + result == expected + +# maximum 32-bit integer input +expect + integers = [4294967295] + result = encode integers + expected = [143, 255, 255, 255, 127] + result == expected + +# two single-byte values +expect + integers = [64, 127] + result = encode integers + expected = [64, 127] + result == expected + +# two multi-byte values +expect + integers = [16384, 1193046] + result = encode integers + expected = [129, 128, 0, 200, 232, 86] + result == expected + +# many multi-byte values +expect + integers = [8192, 1193046, 268435455, 0, 16383, 16384] + result = encode integers + expected = [192, 0, 200, 232, 86, 255, 255, 255, 127, 0, 255, 127, 129, 128, 0] + result == expected + +## +## Decode a series of bytes, producing a series of integers. +## + +# one byte +expect + bytes = [127] + result = decode bytes + expected = Ok [127] + result == expected + +# two bytes +expect + bytes = [192, 0] + result = decode bytes + expected = Ok [8192] + result == expected + +# three bytes +expect + bytes = [255, 255, 127] + result = decode bytes + expected = Ok [2097151] + result == expected + +# four bytes +expect + bytes = [129, 128, 128, 0] + result = decode bytes + expected = Ok [2097152] + result == expected + +# maximum 32-bit integer +expect + bytes = [143, 255, 255, 255, 127] + result = decode bytes + expected = Ok [4294967295] + result == expected + +# incomplete sequence causes error +expect + bytes = [255] + result = decode bytes + result |> Result.isErr + +# incomplete sequence causes error, even if value is zero +expect + bytes = [128] + result = decode bytes + result |> Result.isErr + +# multiple values +expect + bytes = [192, 0, 200, 232, 86, 255, 255, 255, 127, 0, 255, 127, 129, 128, 0] + result = decode bytes + expected = Ok [8192, 1193046, 268435455, 0, 16383, 16384] + result == expected +