From c4d0d836105266ebb80a4e5bf89b6032f56e677b Mon Sep 17 00:00:00 2001 From: Glenn Jackman Date: Thu, 9 Apr 2026 17:10:31 -0400 Subject: [PATCH] Add variable-length-quantity --- config.json | 8 + .../practice/variable-length-quantity/.busted | 5 + .../.docs/instructions.md | 34 ++++ .../.meta/config.json | 19 +++ .../.meta/example.moon | 27 +++ .../.meta/spec_generator.moon | 20 +++ .../variable-length-quantity/.meta/tests.toml | 103 ++++++++++++ .../variable_length_quantity.moon | 7 + .../variable_length_quantity_spec.moon | 157 ++++++++++++++++++ 9 files changed, 380 insertions(+) create mode 100644 exercises/practice/variable-length-quantity/.busted create mode 100644 exercises/practice/variable-length-quantity/.docs/instructions.md create mode 100644 exercises/practice/variable-length-quantity/.meta/config.json create mode 100644 exercises/practice/variable-length-quantity/.meta/example.moon create mode 100644 exercises/practice/variable-length-quantity/.meta/spec_generator.moon create mode 100644 exercises/practice/variable-length-quantity/.meta/tests.toml create mode 100644 exercises/practice/variable-length-quantity/variable_length_quantity.moon create mode 100644 exercises/practice/variable-length-quantity/variable_length_quantity_spec.moon diff --git a/config.json b/config.json index 717c659..3af7674 100644 --- a/config.json +++ b/config.json @@ -738,6 +738,14 @@ "prerequisites": [], "difficulty": 5 }, + { + "slug": "variable-length-quantity", + "name": "Variable Length Quantity", + "uuid": "6738a318-df5b-40bb-9877-04af138f11a7", + "practices": [], + "prerequisites": [], + "difficulty": 5 + }, { "slug": "change", "name": "Change", diff --git a/exercises/practice/variable-length-quantity/.busted b/exercises/practice/variable-length-quantity/.busted new file mode 100644 index 0000000..86b84e7 --- /dev/null +++ b/exercises/practice/variable-length-quantity/.busted @@ -0,0 +1,5 @@ +return { + default = { + ROOT = { '.' } + } +} 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/config.json b/exercises/practice/variable-length-quantity/.meta/config.json new file mode 100644 index 0000000..5901da5 --- /dev/null +++ b/exercises/practice/variable-length-quantity/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "glennj" + ], + "files": { + "solution": [ + "variable_length_quantity.moon" + ], + "test": [ + "variable_length_quantity_spec.moon" + ], + "example": [ + ".meta/example.moon" + ] + }, + "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/example.moon b/exercises/practice/variable-length-quantity/.meta/example.moon new file mode 100644 index 0000000..51b8430 --- /dev/null +++ b/exercises/practice/variable-length-quantity/.meta/example.moon @@ -0,0 +1,27 @@ +List = require 'pl.List' + +{ + encode: (numbers) -> + all_bytes = List! + for num in *numbers + bytes = List! + while num > 0 + byte = num & 0x7F + num >>= 7 + byte |= 0x80 if #bytes > 0 + bytes\put byte + bytes\append 0 if #bytes == 0 + all_bytes\extend bytes + all_bytes + + decode: (bytes) -> + assert bytes[#bytes] & 0x80 == 0, 'incomplete sequence' + numbers = List! + current = 0 + for byte in *bytes + current = (current << 7) | (byte & 0x7F) + if (byte & 0x80) == 0 + numbers\append current + current = 0 + numbers +} \ No newline at end of file diff --git a/exercises/practice/variable-length-quantity/.meta/spec_generator.moon b/exercises/practice/variable-length-quantity/.meta/spec_generator.moon new file mode 100644 index 0000000..38940b0 --- /dev/null +++ b/exercises/practice/variable-length-quantity/.meta/spec_generator.moon @@ -0,0 +1,20 @@ +int_list = (list) -> "{#{table.concat list, ', '}}" + +{ + module_name: 'VariableLengthQuantity', + + generate_test: (case, level) -> + local lines + if case.expected.error + lines = { + "f = -> VariableLengthQuantity.#{case.property} #{int_list case.input.integers}" + "assert.has.errors f, #{quote case.expected.error}" + } + else + lines = { + "result = VariableLengthQuantity.#{case.property} #{int_list case.input.integers}", + "expected = #{int_list case.expected}" + "assert.are.same expected, result" + } + table.concat [indent line, level for line in *lines], '\n' +} 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..53be789 --- /dev/null +++ b/exercises/practice/variable-length-quantity/.meta/tests.toml @@ -0,0 +1,103 @@ +# 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" + +[890bc344-cb80-45af-b316-6806a6971e81] +description = "Encode a series of integers, producing a series of bytes. -> asymmetric 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" + +[4977d113-251b-4d10-a3ad-2f5a7756bb58] +description = "Encode a series of integers, producing a series of bytes. -> asymmetric 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" + +[6731045f-1e00-4192-b5ae-98b22e17e9f7] +description = "Encode a series of integers, producing a series of bytes. -> asymmetric 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" + +[b45ef770-cbba-48c2-bd3c-c6362679516e] +description = "Encode a series of integers, producing a series of bytes. -> asymmetric 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" + +[9be46731-7cd5-415c-b960-48061cbc1154] +description = "Encode a series of integers, producing a series of bytes. -> asymmetric 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/variable_length_quantity.moon b/exercises/practice/variable-length-quantity/variable_length_quantity.moon new file mode 100644 index 0000000..8ffee58 --- /dev/null +++ b/exercises/practice/variable-length-quantity/variable_length_quantity.moon @@ -0,0 +1,7 @@ +{ + encode: (numbers) -> + error 'Implement the encode function' + + decode: (bytes) -> + error 'Implement the decode function' +} \ No newline at end of file diff --git a/exercises/practice/variable-length-quantity/variable_length_quantity_spec.moon b/exercises/practice/variable-length-quantity/variable_length_quantity_spec.moon new file mode 100644 index 0000000..7628e89 --- /dev/null +++ b/exercises/practice/variable-length-quantity/variable_length_quantity_spec.moon @@ -0,0 +1,157 @@ +VariableLengthQuantity = require 'variable_length_quantity' + +describe 'variable-length-quantity', -> + describe 'Encode a series of integers, producing a series of bytes.', -> + it 'zero', -> + result = VariableLengthQuantity.encode {0} + expected = {0} + assert.are.same expected, result + + pending 'arbitrary single byte', -> + result = VariableLengthQuantity.encode {64} + expected = {64} + assert.are.same expected, result + + pending 'asymmetric single byte', -> + result = VariableLengthQuantity.encode {83} + expected = {83} + assert.are.same expected, result + + pending 'largest single byte', -> + result = VariableLengthQuantity.encode {127} + expected = {127} + assert.are.same expected, result + + pending 'smallest double byte', -> + result = VariableLengthQuantity.encode {128} + expected = {129, 0} + assert.are.same expected, result + + pending 'arbitrary double byte', -> + result = VariableLengthQuantity.encode {8192} + expected = {192, 0} + assert.are.same expected, result + + pending 'asymmetric double byte', -> + result = VariableLengthQuantity.encode {173} + expected = {129, 45} + assert.are.same expected, result + + pending 'largest double byte', -> + result = VariableLengthQuantity.encode {16383} + expected = {255, 127} + assert.are.same expected, result + + pending 'smallest triple byte', -> + result = VariableLengthQuantity.encode {16384} + expected = {129, 128, 0} + assert.are.same expected, result + + pending 'arbitrary triple byte', -> + result = VariableLengthQuantity.encode {1048576} + expected = {192, 128, 0} + assert.are.same expected, result + + pending 'asymmetric triple byte', -> + result = VariableLengthQuantity.encode {120220} + expected = {135, 171, 28} + assert.are.same expected, result + + pending 'largest triple byte', -> + result = VariableLengthQuantity.encode {2097151} + expected = {255, 255, 127} + assert.are.same expected, result + + pending 'smallest quadruple byte', -> + result = VariableLengthQuantity.encode {2097152} + expected = {129, 128, 128, 0} + assert.are.same expected, result + + pending 'arbitrary quadruple byte', -> + result = VariableLengthQuantity.encode {134217728} + expected = {192, 128, 128, 0} + assert.are.same expected, result + + pending 'asymmetric quadruple byte', -> + result = VariableLengthQuantity.encode {3503876} + expected = {129, 213, 238, 4} + assert.are.same expected, result + + pending 'largest quadruple byte', -> + result = VariableLengthQuantity.encode {268435455} + expected = {255, 255, 255, 127} + assert.are.same expected, result + + pending 'smallest quintuple byte', -> + result = VariableLengthQuantity.encode {268435456} + expected = {129, 128, 128, 128, 0} + assert.are.same expected, result + + pending 'arbitrary quintuple byte', -> + result = VariableLengthQuantity.encode {4278190080} + expected = {143, 248, 128, 128, 0} + assert.are.same expected, result + + pending 'asymmetric quintuple byte', -> + result = VariableLengthQuantity.encode {2254790917} + expected = {136, 179, 149, 194, 5} + assert.are.same expected, result + + pending 'maximum 32-bit integer input', -> + result = VariableLengthQuantity.encode {4294967295} + expected = {143, 255, 255, 255, 127} + assert.are.same expected, result + + pending 'two single-byte values', -> + result = VariableLengthQuantity.encode {64, 127} + expected = {64, 127} + assert.are.same expected, result + + pending 'two multi-byte values', -> + result = VariableLengthQuantity.encode {16384, 1193046} + expected = {129, 128, 0, 200, 232, 86} + assert.are.same expected, result + + pending 'many multi-byte values', -> + result = VariableLengthQuantity.encode {8192, 1193046, 268435455, 0, 16383, 16384} + expected = {192, 0, 200, 232, 86, 255, 255, 255, 127, 0, 255, 127, 129, 128, 0} + assert.are.same expected, result + + describe 'Decode a series of bytes, producing a series of integers.', -> + pending 'one byte', -> + result = VariableLengthQuantity.decode {127} + expected = {127} + assert.are.same expected, result + + pending 'two bytes', -> + result = VariableLengthQuantity.decode {192, 0} + expected = {8192} + assert.are.same expected, result + + pending 'three bytes', -> + result = VariableLengthQuantity.decode {255, 255, 127} + expected = {2097151} + assert.are.same expected, result + + pending 'four bytes', -> + result = VariableLengthQuantity.decode {129, 128, 128, 0} + expected = {2097152} + assert.are.same expected, result + + pending 'maximum 32-bit integer', -> + result = VariableLengthQuantity.decode {143, 255, 255, 255, 127} + expected = {4294967295} + assert.are.same expected, result + + pending 'incomplete sequence causes error', -> + f = -> VariableLengthQuantity.decode {255} + assert.has.errors f, 'incomplete sequence' + + pending 'incomplete sequence causes error, even if value is zero', -> + f = -> VariableLengthQuantity.decode {128} + assert.has.errors f, 'incomplete sequence' + + pending 'multiple values', -> + result = VariableLengthQuantity.decode {192, 0, 200, 232, 86, 255, 255, 255, 127, 0, 255, 127, 129, 128, 0} + expected = {8192, 1193046, 268435455, 0, 16383, 16384} + assert.are.same expected, result