From 5db14cef7d5b5215ab4913089364134fc42cd4dd Mon Sep 17 00:00:00 2001 From: habere-et-dispertire Date: Wed, 11 Dec 2024 22:42:06 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=92=9C=C2=B2=E2=81=B7=20(#740)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -- 🤝 [New Exercise Contributions](https://forum.exercism.org/t/new-exercise-contributions/4077) ( forum dot exercism dot org ) --- config.json | 8 + .../.docs/instructions.md | 34 ++++ .../.meta/config.json | 19 ++ .../lib/VariableLengthQuantity.rakumod | 35 ++++ .../t/variable-length-quantity.rakutest | 1 + .../.meta/template-data.yaml | 77 ++++++++ .../variable-length-quantity/.meta/tests.toml | 88 +++++++++ .../lib/VariableLengthQuantity.rakumod | 9 + .../t/variable-length-quantity.rakutest | 184 ++++++++++++++++++ 9 files changed, 455 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/solutions/lib/VariableLengthQuantity.rakumod create mode 120000 exercises/practice/variable-length-quantity/.meta/solutions/t/variable-length-quantity.rakutest create mode 100644 exercises/practice/variable-length-quantity/.meta/template-data.yaml create mode 100644 exercises/practice/variable-length-quantity/.meta/tests.toml create mode 100644 exercises/practice/variable-length-quantity/lib/VariableLengthQuantity.rakumod create mode 100755 exercises/practice/variable-length-quantity/t/variable-length-quantity.rakutest diff --git a/config.json b/config.json index 5c6eedee..38b9a735 100644 --- a/config.json +++ b/config.json @@ -727,6 +727,14 @@ "practices": [], "prerequisites": [], "difficulty": 1 + }, + { + "slug": "variable-length-quantity", + "name": "Variable Length Quantity", + "uuid": "c3af31db-38d6-4d5c-927c-5aaf3078e6c3", + "practices": [], + "prerequisites": [], + "difficulty": 1 } ] }, 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 00000000..50125482 --- /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 00000000..6a88c0c8 --- /dev/null +++ b/exercises/practice/variable-length-quantity/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "habere-et-dispertire" + ], + "files": { + "solution": [ + "lib/VariableLengthQuantity.rakumod" + ], + "test": [ + "t/variable-length-quantity.rakutest" + ], + "example": [ + ".meta/solutions/lib/VariableLengthQuantity.rakumod" + ] + }, + "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/solutions/lib/VariableLengthQuantity.rakumod b/exercises/practice/variable-length-quantity/.meta/solutions/lib/VariableLengthQuantity.rakumod new file mode 100644 index 00000000..af06b5a1 --- /dev/null +++ b/exercises/practice/variable-length-quantity/.meta/solutions/lib/VariableLengthQuantity.rakumod @@ -0,0 +1,35 @@ +unit module VariableLengthQuantity; + +grammar VLQ { + token TOP { + } + token train { [ [ ]+ ]? } + token cargo { [ 0 | 1 ] ** 7 } + token coupled { 1 } + token caboose { 0 } +} + +class VLQ::Decode { + method TOP ($/) { make Array.new: map *.made, $ } + method train ($/) { make $.map( *.made ).join.parse-base: 2 } + method cargo ($/) { make map *.Str, $/ } +} + +sub encode-vlq (@raw --> Array()) is export { + given flat gather + take .[^.end].map( * +| 0b10000000 ), .[.end] + for @raw.map: *.base(2).flip.comb(7).flip.words.map: *.parse-base: 2 + -> @compressed { + fail unless @raw eqv VLQ.parse( + actions => VLQ::Decode, + @compressed.map( *.base(2).fmt: '%08d' ).join + ).made; + return @compressed; + } +} + +sub decode-vlq (@compressed --> Array()) is export { + VLQ.parse( + actions => VLQ::Decode, + @compressed.map( *.base( 2 ).fmt: '%08d' ).join + ).made // fail +} diff --git a/exercises/practice/variable-length-quantity/.meta/solutions/t/variable-length-quantity.rakutest b/exercises/practice/variable-length-quantity/.meta/solutions/t/variable-length-quantity.rakutest new file mode 120000 index 00000000..7b892332 --- /dev/null +++ b/exercises/practice/variable-length-quantity/.meta/solutions/t/variable-length-quantity.rakutest @@ -0,0 +1 @@ +../../../t/variable-length-quantity.rakutest \ No newline at end of file diff --git a/exercises/practice/variable-length-quantity/.meta/template-data.yaml b/exercises/practice/variable-length-quantity/.meta/template-data.yaml new file mode 100644 index 00000000..3b0c1385 --- /dev/null +++ b/exercises/practice/variable-length-quantity/.meta/template-data.yaml @@ -0,0 +1,77 @@ +properties: + encode: + test: |- + sprintf(q :to 'END', %case.Array.raku, %case.Array.raku, %case.raku); + cmp-ok( + encode-vlq(%s), + "eqv", + %s, + %s, + ); + END + decode: + test: |- + if %case:exists { + sprintf(q :to 'END', %case.Array.raku, %case.raku); + dies-ok( + { decode-vlq(%s) }, + %s, + ); + END + } + else { + sprintf(q :to 'END', %case.Array.raku, %case.Array.raku, %case.raku); + cmp-ok( + decode-vlq(%s), + "eqv", + %s, + %s, + ); + END + } + + +unit: module +example: |- + grammar VLQ { + token TOP { + } + token train { [ [ ]+ ]? } + token cargo { [ 0 | 1 ] ** 7 } + token coupled { 1 } + token caboose { 0 } + } + + class VLQ::Decode { + method TOP ($/) { make Array.new: map *.made, $ } + method train ($/) { make $.map( *.made ).join.parse-base: 2 } + method cargo ($/) { make map *.Str, $/ } + } + + sub encode-vlq (@raw --> Array()) is export { + given flat gather + take .[^.end].map( * +| 0b10000000 ), .[.end] + for @raw.map: *.base(2).flip.comb(7).flip.words.map: *.parse-base: 2 + -> @compressed { + fail unless @raw eqv VLQ.parse( + actions => VLQ::Decode, + @compressed.map( *.base(2).fmt: '%08d' ).join + ).made; + return @compressed; + } + } + + sub decode-vlq (@compressed --> Array()) is export { + VLQ.parse( + actions => VLQ::Decode, + @compressed.map( *.base( 2 ).fmt: '%08d' ).join + ).made // fail + } + +stub: |- + sub encode-vlq ( @raw --> Array() ) is export { + ... + } + + sub decode-vlq ( @compressed --> Array() ) is export { + ... + } 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 00000000..c9af549f --- /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/lib/VariableLengthQuantity.rakumod b/exercises/practice/variable-length-quantity/lib/VariableLengthQuantity.rakumod new file mode 100644 index 00000000..0ed2f69e --- /dev/null +++ b/exercises/practice/variable-length-quantity/lib/VariableLengthQuantity.rakumod @@ -0,0 +1,9 @@ +unit module VariableLengthQuantity; + +sub encode-vlq ( @raw --> Array() ) is export { + ... +} + +sub decode-vlq ( @compressed --> Array() ) is export { + ... +} diff --git a/exercises/practice/variable-length-quantity/t/variable-length-quantity.rakutest b/exercises/practice/variable-length-quantity/t/variable-length-quantity.rakutest new file mode 100755 index 00000000..39d5e100 --- /dev/null +++ b/exercises/practice/variable-length-quantity/t/variable-length-quantity.rakutest @@ -0,0 +1,184 @@ +#!/usr/bin/env raku +use Test; +use lib $?FILE.IO.parent(2).add('lib'); +use VariableLengthQuantity; + +cmp-ok( # begin: 35c9db2e-f781-4c52-b73b-8e76427defd0 + encode-vlq([0]), + "eqv", + [0], + "Encode a series of integers, producing a series of bytes.: zero", +); # end: 35c9db2e-f781-4c52-b73b-8e76427defd0 + +cmp-ok( # begin: be44d299-a151-4604-a10e-d4b867f41540 + encode-vlq([64]), + "eqv", + [64], + "Encode a series of integers, producing a series of bytes.: arbitrary single byte", +); # end: be44d299-a151-4604-a10e-d4b867f41540 + +cmp-ok( # begin: ea399615-d274-4af6-bbef-a1c23c9e1346 + encode-vlq([127]), + "eqv", + [127], + "Encode a series of integers, producing a series of bytes.: largest single byte", +); # end: ea399615-d274-4af6-bbef-a1c23c9e1346 + +cmp-ok( # begin: 77b07086-bd3f-4882-8476-8dcafee79b1c + encode-vlq([128]), + "eqv", + [129, 0], + "Encode a series of integers, producing a series of bytes.: smallest double byte", +); # end: 77b07086-bd3f-4882-8476-8dcafee79b1c + +cmp-ok( # begin: 63955a49-2690-4e22-a556-0040648d6b2d + encode-vlq([8192]), + "eqv", + [192, 0], + "Encode a series of integers, producing a series of bytes.: arbitrary double byte", +); # end: 63955a49-2690-4e22-a556-0040648d6b2d + +cmp-ok( # begin: 29da7031-0067-43d3-83a7-4f14b29ed97a + encode-vlq([16383]), + "eqv", + [255, 127], + "Encode a series of integers, producing a series of bytes.: largest double byte", +); # end: 29da7031-0067-43d3-83a7-4f14b29ed97a + +cmp-ok( # begin: 3345d2e3-79a9-4999-869e-d4856e3a8e01 + encode-vlq([16384]), + "eqv", + [129, 128, 0], + "Encode a series of integers, producing a series of bytes.: smallest triple byte", +); # end: 3345d2e3-79a9-4999-869e-d4856e3a8e01 + +cmp-ok( # begin: 5df0bc2d-2a57-4300-a653-a75ee4bd0bee + encode-vlq([1048576]), + "eqv", + [192, 128, 0], + "Encode a series of integers, producing a series of bytes.: arbitrary triple byte", +); # end: 5df0bc2d-2a57-4300-a653-a75ee4bd0bee + +cmp-ok( # begin: f51d8539-312d-4db1-945c-250222c6aa22 + encode-vlq([2097151]), + "eqv", + [255, 255, 127], + "Encode a series of integers, producing a series of bytes.: largest triple byte", +); # end: f51d8539-312d-4db1-945c-250222c6aa22 + +cmp-ok( # begin: da78228b-544f-47b7-8bfe-d16b35bbe570 + encode-vlq([2097152]), + "eqv", + [129, 128, 128, 0], + "Encode a series of integers, producing a series of bytes.: smallest quadruple byte", +); # end: da78228b-544f-47b7-8bfe-d16b35bbe570 + +cmp-ok( # begin: 11ed3469-a933-46f1-996f-2231e05d7bb6 + encode-vlq([134217728]), + "eqv", + [192, 128, 128, 0], + "Encode a series of integers, producing a series of bytes.: arbitrary quadruple byte", +); # end: 11ed3469-a933-46f1-996f-2231e05d7bb6 + +cmp-ok( # begin: d5f3f3c3-e0f1-4e7f-aad0-18a44f223d1c + encode-vlq([268435455]), + "eqv", + [255, 255, 255, 127], + "Encode a series of integers, producing a series of bytes.: largest quadruple byte", +); # end: d5f3f3c3-e0f1-4e7f-aad0-18a44f223d1c + +cmp-ok( # begin: 91a18b33-24e7-4bfb-bbca-eca78ff4fc47 + encode-vlq([268435456]), + "eqv", + [129, 128, 128, 128, 0], + "Encode a series of integers, producing a series of bytes.: smallest quintuple byte", +); # end: 91a18b33-24e7-4bfb-bbca-eca78ff4fc47 + +cmp-ok( # begin: 5f34ff12-2952-4669-95fe-2d11b693d331 + encode-vlq([4278190080]), + "eqv", + [143, 248, 128, 128, 0], + "Encode a series of integers, producing a series of bytes.: arbitrary quintuple byte", +); # end: 5f34ff12-2952-4669-95fe-2d11b693d331 + +cmp-ok( # begin: 7489694b-88c3-4078-9864-6fe802411009 + encode-vlq([4294967295]), + "eqv", + [143, 255, 255, 255, 127], + "Encode a series of integers, producing a series of bytes.: maximum 32-bit integer input", +); # end: 7489694b-88c3-4078-9864-6fe802411009 + +cmp-ok( # begin: f9b91821-cada-4a73-9421-3c81d6ff3661 + encode-vlq([64, 127]), + "eqv", + [64, 127], + "Encode a series of integers, producing a series of bytes.: two single-byte values", +); # end: f9b91821-cada-4a73-9421-3c81d6ff3661 + +cmp-ok( # begin: 68694449-25d2-4974-ba75-fa7bb36db212 + encode-vlq([16384, 1193046]), + "eqv", + [129, 128, 0, 200, 232, 86], + "Encode a series of integers, producing a series of bytes.: two multi-byte values", +); # end: 68694449-25d2-4974-ba75-fa7bb36db212 + +cmp-ok( # begin: 51a06b5c-de1b-4487-9a50-9db1b8930d85 + encode-vlq([8192, 1193046, 268435455, 0, 16383, 16384]), + "eqv", + [192, 0, 200, 232, 86, 255, 255, 255, 127, 0, 255, 127, 129, 128, 0], + "Encode a series of integers, producing a series of bytes.: many multi-byte values", +); # end: 51a06b5c-de1b-4487-9a50-9db1b8930d85 + +cmp-ok( # begin: baa73993-4514-4915-bac0-f7f585e0e59a + decode-vlq([127]), + "eqv", + [127], + "Decode a series of bytes, producing a series of integers.: one byte", +); # end: baa73993-4514-4915-bac0-f7f585e0e59a + +cmp-ok( # begin: 72e94369-29f9-46f2-8c95-6c5b7a595aee + decode-vlq([192, 0]), + "eqv", + [8192], + "Decode a series of bytes, producing a series of integers.: two bytes", +); # end: 72e94369-29f9-46f2-8c95-6c5b7a595aee + +cmp-ok( # begin: df5a44c4-56f7-464e-a997-1db5f63ce691 + decode-vlq([255, 255, 127]), + "eqv", + [2097151], + "Decode a series of bytes, producing a series of integers.: three bytes", +); # end: df5a44c4-56f7-464e-a997-1db5f63ce691 + +cmp-ok( # begin: 1bb58684-f2dc-450a-8406-1f3452aa1947 + decode-vlq([129, 128, 128, 0]), + "eqv", + [2097152], + "Decode a series of bytes, producing a series of integers.: four bytes", +); # end: 1bb58684-f2dc-450a-8406-1f3452aa1947 + +cmp-ok( # begin: cecd5233-49f1-4dd1-a41a-9840a40f09cd + decode-vlq([143, 255, 255, 255, 127]), + "eqv", + [4294967295], + "Decode a series of bytes, producing a series of integers.: maximum 32-bit integer", +); # end: cecd5233-49f1-4dd1-a41a-9840a40f09cd + +dies-ok( # begin: e7d74ba3-8b8e-4bcb-858d-d08302e15695 + { decode-vlq([255]) }, + "Decode a series of bytes, producing a series of integers.: incomplete sequence causes error", +); # end: e7d74ba3-8b8e-4bcb-858d-d08302e15695 + +dies-ok( # begin: aa378291-9043-4724-bc53-aca1b4a3fcb6 + { decode-vlq([128]) }, + "Decode a series of bytes, producing a series of integers.: incomplete sequence causes error, even if value is zero", +); # end: aa378291-9043-4724-bc53-aca1b4a3fcb6 + +cmp-ok( # begin: a91e6f5a-c64a-48e3-8a75-ce1a81e0ebee + decode-vlq([192, 0, 200, 232, 86, 255, 255, 255, 127, 0, 255, 127, 129, 128, 0]), + "eqv", + [8192, 1193046, 268435455, 0, 16383, 16384], + "Decode a series of bytes, producing a series of integers.: multiple values", +); # end: a91e6f5a-c64a-48e3-8a75-ce1a81e0ebee + +done-testing;