From 5e4ce5933e296a759f77f3391613ef9396a02ba9 Mon Sep 17 00:00:00 2001 From: Eric Willigers Date: Mon, 13 Oct 2025 11:15:22 +1100 Subject: [PATCH] Add variable-length-quantity --- config.json | 8 + .../.docs/instructions.md | 34 +++ .../.meta/config.json | 19 ++ .../variable-length-quantity/.meta/tests.toml | 103 ++++++++ .../practice/variable-length-quantity/dub.sdl | 2 + .../example/variable_length_quantity.d | 51 ++++ .../source/variable_length_quantity.d | 237 ++++++++++++++++++ 7 files changed, 454 insertions(+) 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/tests.toml create mode 100644 exercises/practice/variable-length-quantity/dub.sdl create mode 100644 exercises/practice/variable-length-quantity/example/variable_length_quantity.d create mode 100644 exercises/practice/variable-length-quantity/source/variable_length_quantity.d diff --git a/config.json b/config.json index d0c7010..1705f3b 100644 --- a/config.json +++ b/config.json @@ -754,6 +754,14 @@ "prerequisites": [], "difficulty": 5 }, + { + "slug": "variable-length-quantity", + "name": "Variable Length Quantity", + "uuid": "754b9ada-00c5-4dbe-a97e-fc0c049e14cc", + "practices": [], + "prerequisites": [], + "difficulty": 5 + }, { "slug": "yacht", "name": "Yacht", 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..20ef9b8 --- /dev/null +++ b/exercises/practice/variable-length-quantity/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "keiravillekode" + ], + "files": { + "solution": [ + "source/variable_length_quantity.d" + ], + "test": [ + "source/variable_length_quantity.d" + ], + "example": [ + "example/variable_length_quantity.d" + ] + }, + "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/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/dub.sdl b/exercises/practice/variable-length-quantity/dub.sdl new file mode 100644 index 0000000..60bc822 --- /dev/null +++ b/exercises/practice/variable-length-quantity/dub.sdl @@ -0,0 +1,2 @@ +name "variable-length-quantity" +buildRequirements "disallowDeprecations" diff --git a/exercises/practice/variable-length-quantity/example/variable_length_quantity.d b/exercises/practice/variable-length-quantity/example/variable_length_quantity.d new file mode 100644 index 0000000..215e69a --- /dev/null +++ b/exercises/practice/variable-length-quantity/example/variable_length_quantity.d @@ -0,0 +1,51 @@ +module variable_length_quantity; + +import std.algorithm.mutation; + +pure ubyte[] encode(immutable uint[] integers) +{ + ubyte[] result; + auto index = integers.length; + while (index > 0) + { + index--; + uint integer = integers[index]; + + result ~= (integer & 0x7f); + integer >>= 7; + while (integer != 0) + { + result ~= ((integer & 0x7f) | 0x80); + integer >>= 7; + } + } + + result.reverse(); + return result; +} + +pure uint[] decode(immutable ubyte[] integers) +{ + uint[] result; + uint integer; + bool incomplete = false; + foreach (data; integers) + { + integer = (integer << 7) | (data & 0x7f); + if ((data & 0x80) == 0) + { + result ~= integer; + integer = 0; + incomplete = false; + } + else + { + incomplete = true; + } + } + if (incomplete) + { + throw new Exception("incomplete sequence"); + } + return result; +} diff --git a/exercises/practice/variable-length-quantity/source/variable_length_quantity.d b/exercises/practice/variable-length-quantity/source/variable_length_quantity.d new file mode 100644 index 0000000..386619e --- /dev/null +++ b/exercises/practice/variable-length-quantity/source/variable_length_quantity.d @@ -0,0 +1,237 @@ +module variable_length_quantity; + +pure ubyte[] encode(immutable uint[] integers) +{ + // implement this function +} + +pure uint[] decode(immutable ubyte[] integers) +{ + // implement this function +} + +unittest +{ + import std.algorithm.comparison : equal; + import std.exception : assertThrown; + + immutable int allTestsEnabled = 0; + + // Encode - zero + { + immutable uint[] integers = [ 0x0 ]; + immutable ubyte[] expected = [ 0x0 ]; + assert(equal(encode(integers), expected)); + } + + static if (allTestsEnabled) + { + // Encode - arbitrary single byte + { + immutable uint[] integers = [ 0x40 ]; + immutable ubyte[] expected = [ 0x40 ]; + assert(equal(encode(integers), expected)); + } + + // Encode - asymmetric single byte + { + immutable uint[] integers = [ 0x53 ]; + immutable ubyte[] expected = [ 0x53 ]; + assert(equal(encode(integers), expected)); + } + + // Encode - largest single byte + { + immutable uint[] integers = [ 0x7f ]; + immutable ubyte[] expected = [ 0x7f ]; + assert(equal(encode(integers), expected)); + } + + // Encode - smallest double byte + { + immutable uint[] integers = [ 0x80 ]; + immutable ubyte[] expected = [ 0x81, 0x0 ]; + assert(equal(encode(integers), expected)); + } + + // Encode - arbitrary double byte + { + immutable uint[] integers = [ 0x2000 ]; + immutable ubyte[] expected = [ 0xc0, 0x0 ]; + assert(equal(encode(integers), expected)); + } + + // Encode - asymmetric double byte + { + immutable uint[] integers = [ 0xad ]; + immutable ubyte[] expected = [ 0x81, 0x2d ]; + assert(equal(encode(integers), expected)); + } + + // Encode - largest double byte + { + immutable uint[] integers = [ 0x3fff ]; + immutable ubyte[] expected = [ 0xff, 0x7f ]; + assert(equal(encode(integers), expected)); + } + + // Encode - smallest triple byte + { + immutable uint[] integers = [ 0x4000 ]; + immutable ubyte[] expected = [ 0x81, 0x80, 0x0 ]; + assert(equal(encode(integers), expected)); + } + + // Encode - arbitrary triple byte + { + immutable uint[] integers = [ 0x100000 ]; + immutable ubyte[] expected = [ 0xc0, 0x80, 0x0 ]; + assert(equal(encode(integers), expected)); + } + + // Encode - asymmetric triple byte + { + immutable uint[] integers = [ 0x1d59c ]; + immutable ubyte[] expected = [ 0x87, 0xab, 0x1c ]; + assert(equal(encode(integers), expected)); + } + + // Encode - largest triple byte + { + immutable uint[] integers = [ 0x1fffff ]; + immutable ubyte[] expected = [ 0xff, 0xff, 0x7f ]; + assert(equal(encode(integers), expected)); + } + + // Encode - smallest quadruple byte + { + immutable uint[] integers = [ 0x200000 ]; + immutable ubyte[] expected = [ 0x81, 0x80, 0x80, 0x0 ]; + assert(equal(encode(integers), expected)); + } + + // Encode - arbitrary quadruple byte + { + immutable uint[] integers = [ 0x8000000 ]; + immutable ubyte[] expected = [ 0xc0, 0x80, 0x80, 0x0 ]; + assert(equal(encode(integers), expected)); + } + + // Encode - asymmetric quadruple byte + { + immutable uint[] integers = [ 0x357704 ]; + immutable ubyte[] expected = [ 0x81, 0xd5, 0xee, 0x4 ]; + assert(equal(encode(integers), expected)); + } + + // Encode - largest quadruple byte + { + immutable uint[] integers = [ 0xfffffff ]; + immutable ubyte[] expected = [ 0xff, 0xff, 0xff, 0x7f ]; + assert(equal(encode(integers), expected)); + } + + // Encode - smallest quintuple byte + { + immutable uint[] integers = [ 0x10000000 ]; + immutable ubyte[] expected = [ 0x81, 0x80, 0x80, 0x80, 0x0 ]; + assert(equal(encode(integers), expected)); + } + + // Encode - arbitrary quintuple byte + { + immutable uint[] integers = [ 0xff000000 ]; + immutable ubyte[] expected = [ 0x8f, 0xf8, 0x80, 0x80, 0x0 ]; + assert(equal(encode(integers), expected)); + } + + // Encode - asymmetric quintuple byte + { + immutable uint[] integers = [ 0x86656105 ]; + immutable ubyte[] expected = [ 0x88, 0xb3, 0x95, 0xc2, 0x5 ]; + assert(equal(encode(integers), expected)); + } + + // Encode - maximum 32-bit integer input + { + immutable uint[] integers = [ 0xffffffff ]; + immutable ubyte[] expected = [ 0x8f, 0xff, 0xff, 0xff, 0x7f ]; + assert(equal(encode(integers), expected)); + } + + // Encode - two single-byte values + { + immutable uint[] integers = [ 0x40, 0x7f ]; + immutable ubyte[] expected = [ 0x40, 0x7f ]; + assert(equal(encode(integers), expected)); + } + + // Encode - two multi-byte values + { + immutable uint[] integers = [ 0x4000, 0x123456 ]; + immutable ubyte[] expected = [ 0x81, 0x80, 0x0, 0xc8, 0xe8, 0x56 ]; + assert(equal(encode(integers), expected)); + } + + // Encode - many multi-byte values + { + immutable uint[] integers = [ 0x2000, 0x123456, 0xfffffff, 0x0, 0x3fff, 0x4000 ]; + immutable ubyte[] expected = [ 0xc0, 0x0, 0xc8, 0xe8, 0x56, 0xff, 0xff, 0xff, 0x7f, 0x0, 0xff, 0x7f, 0x81, 0x80, 0x0 ]; + assert(equal(encode(integers), expected)); + } + + // Decode - one byte + { + immutable ubyte[] integers = [ 0x7f ]; + immutable uint[] expected = [ 0x7f ]; + assert(equal(decode(integers), expected)); + } + + // Decode - two bytes + { + immutable ubyte[] integers = [ 0xc0, 0x0 ]; + immutable uint[] expected = [ 0x2000 ]; + assert(equal(decode(integers), expected)); + } + + // Decode - three bytes + { + immutable ubyte[] integers = [ 0xff, 0xff, 0x7f ]; + immutable uint[] expected = [ 0x1fffff ]; + assert(equal(decode(integers), expected)); + } + + // Decode - four bytes + { + immutable ubyte[] integers = [ 0x81, 0x80, 0x80, 0x0 ]; + immutable uint[] expected = [ 0x200000 ]; + assert(equal(decode(integers), expected)); + } + + // Decode - maximum 32-bit integer + { + immutable ubyte[] integers = [ 0x8f, 0xff, 0xff, 0xff, 0x7f ]; + immutable uint[] expected = [ 0xffffffff ]; + assert(equal(decode(integers), expected)); + } + + // Decode - incomplete sequence causes error + { + immutable ubyte[] integers = [ 0xff ]; + assertThrown(decode(integers)); + } + + // Decode - incomplete sequence causes error, even if value is zero + { + immutable ubyte[] integers = [ 0x80 ]; + assertThrown(decode(integers)); + } + + // Decode - multiple values + { + immutable ubyte[] integers = [ 0xc0, 0x0, 0xc8, 0xe8, 0x56, 0xff, 0xff, 0xff, 0x7f, 0x0, 0xff, 0x7f, 0x81, 0x80, 0x0 ]; + immutable uint[] expected = [ 0x2000, 0x123456, 0xfffffff, 0x0, 0x3fff, 0x4000 ]; + assert(equal(decode(integers), expected)); + } + } +}