Skip to content

Commit 5e75c36

Browse files
authored
Add variable-length-quantity (#138)
1 parent 1a2985f commit 5e75c36

9 files changed

Lines changed: 380 additions & 0 deletions

File tree

config.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,14 @@
738738
"prerequisites": [],
739739
"difficulty": 5
740740
},
741+
{
742+
"slug": "variable-length-quantity",
743+
"name": "Variable Length Quantity",
744+
"uuid": "6738a318-df5b-40bb-9877-04af138f11a7",
745+
"practices": [],
746+
"prerequisites": [],
747+
"difficulty": 5
748+
},
741749
{
742750
"slug": "change",
743751
"name": "Change",
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
return {
2+
default = {
3+
ROOT = { '.' }
4+
}
5+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Instructions
2+
3+
Implement variable length quantity encoding and decoding.
4+
5+
The goal of this exercise is to implement [VLQ][vlq] encoding/decoding.
6+
7+
In short, the goal of this encoding is to encode integer values in a way that would save bytes.
8+
Only the first 7 bits of each byte are significant (right-justified; sort of like an ASCII byte).
9+
So, if you have a 32-bit value, you have to unpack it into a series of 7-bit bytes.
10+
Of course, you will have a variable number of bytes depending upon your integer.
11+
To indicate which is the last byte of the series, you leave bit #7 clear.
12+
In all of the preceding bytes, you set bit #7.
13+
14+
So, if an integer is between `0-127`, it can be represented as one byte.
15+
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.
16+
Here are examples of integers as 32-bit values, and the variable length quantities that they translate to:
17+
18+
```text
19+
NUMBER VARIABLE QUANTITY
20+
00000000 00
21+
00000040 40
22+
0000007F 7F
23+
00000080 81 00
24+
00002000 C0 00
25+
00003FFF FF 7F
26+
00004000 81 80 00
27+
00100000 C0 80 00
28+
001FFFFF FF FF 7F
29+
00200000 81 80 80 00
30+
08000000 C0 80 80 00
31+
0FFFFFFF FF FF FF 7F
32+
```
33+
34+
[vlq]: https://en.wikipedia.org/wiki/Variable-length_quantity
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"authors": [
3+
"glennj"
4+
],
5+
"files": {
6+
"solution": [
7+
"variable_length_quantity.moon"
8+
],
9+
"test": [
10+
"variable_length_quantity_spec.moon"
11+
],
12+
"example": [
13+
".meta/example.moon"
14+
]
15+
},
16+
"blurb": "Implement variable length quantity encoding and decoding.",
17+
"source": "A poor Splice developer having to implement MIDI encoding/decoding.",
18+
"source_url": "https://splice.com"
19+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
List = require 'pl.List'
2+
3+
{
4+
encode: (numbers) ->
5+
all_bytes = List!
6+
for num in *numbers
7+
bytes = List!
8+
while num > 0
9+
byte = num & 0x7F
10+
num >>= 7
11+
byte |= 0x80 if #bytes > 0
12+
bytes\put byte
13+
bytes\append 0 if #bytes == 0
14+
all_bytes\extend bytes
15+
all_bytes
16+
17+
decode: (bytes) ->
18+
assert bytes[#bytes] & 0x80 == 0, 'incomplete sequence'
19+
numbers = List!
20+
current = 0
21+
for byte in *bytes
22+
current = (current << 7) | (byte & 0x7F)
23+
if (byte & 0x80) == 0
24+
numbers\append current
25+
current = 0
26+
numbers
27+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
int_list = (list) -> "{#{table.concat list, ', '}}"
2+
3+
{
4+
module_name: 'VariableLengthQuantity',
5+
6+
generate_test: (case, level) ->
7+
local lines
8+
if case.expected.error
9+
lines = {
10+
"f = -> VariableLengthQuantity.#{case.property} #{int_list case.input.integers}"
11+
"assert.has.errors f, #{quote case.expected.error}"
12+
}
13+
else
14+
lines = {
15+
"result = VariableLengthQuantity.#{case.property} #{int_list case.input.integers}",
16+
"expected = #{int_list case.expected}"
17+
"assert.are.same expected, result"
18+
}
19+
table.concat [indent line, level for line in *lines], '\n'
20+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
# This is an auto-generated file.
2+
#
3+
# Regenerating this file via `configlet sync` will:
4+
# - Recreate every `description` key/value pair
5+
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
6+
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
7+
# - Preserve any other key/value pair
8+
#
9+
# As user-added comments (using the # character) will be removed when this file
10+
# is regenerated, comments can be added via a `comment` key.
11+
12+
[35c9db2e-f781-4c52-b73b-8e76427defd0]
13+
description = "Encode a series of integers, producing a series of bytes. -> zero"
14+
15+
[be44d299-a151-4604-a10e-d4b867f41540]
16+
description = "Encode a series of integers, producing a series of bytes. -> arbitrary single byte"
17+
18+
[890bc344-cb80-45af-b316-6806a6971e81]
19+
description = "Encode a series of integers, producing a series of bytes. -> asymmetric single byte"
20+
21+
[ea399615-d274-4af6-bbef-a1c23c9e1346]
22+
description = "Encode a series of integers, producing a series of bytes. -> largest single byte"
23+
24+
[77b07086-bd3f-4882-8476-8dcafee79b1c]
25+
description = "Encode a series of integers, producing a series of bytes. -> smallest double byte"
26+
27+
[63955a49-2690-4e22-a556-0040648d6b2d]
28+
description = "Encode a series of integers, producing a series of bytes. -> arbitrary double byte"
29+
30+
[4977d113-251b-4d10-a3ad-2f5a7756bb58]
31+
description = "Encode a series of integers, producing a series of bytes. -> asymmetric double byte"
32+
33+
[29da7031-0067-43d3-83a7-4f14b29ed97a]
34+
description = "Encode a series of integers, producing a series of bytes. -> largest double byte"
35+
36+
[3345d2e3-79a9-4999-869e-d4856e3a8e01]
37+
description = "Encode a series of integers, producing a series of bytes. -> smallest triple byte"
38+
39+
[5df0bc2d-2a57-4300-a653-a75ee4bd0bee]
40+
description = "Encode a series of integers, producing a series of bytes. -> arbitrary triple byte"
41+
42+
[6731045f-1e00-4192-b5ae-98b22e17e9f7]
43+
description = "Encode a series of integers, producing a series of bytes. -> asymmetric triple byte"
44+
45+
[f51d8539-312d-4db1-945c-250222c6aa22]
46+
description = "Encode a series of integers, producing a series of bytes. -> largest triple byte"
47+
48+
[da78228b-544f-47b7-8bfe-d16b35bbe570]
49+
description = "Encode a series of integers, producing a series of bytes. -> smallest quadruple byte"
50+
51+
[11ed3469-a933-46f1-996f-2231e05d7bb6]
52+
description = "Encode a series of integers, producing a series of bytes. -> arbitrary quadruple byte"
53+
54+
[b45ef770-cbba-48c2-bd3c-c6362679516e]
55+
description = "Encode a series of integers, producing a series of bytes. -> asymmetric quadruple byte"
56+
57+
[d5f3f3c3-e0f1-4e7f-aad0-18a44f223d1c]
58+
description = "Encode a series of integers, producing a series of bytes. -> largest quadruple byte"
59+
60+
[91a18b33-24e7-4bfb-bbca-eca78ff4fc47]
61+
description = "Encode a series of integers, producing a series of bytes. -> smallest quintuple byte"
62+
63+
[5f34ff12-2952-4669-95fe-2d11b693d331]
64+
description = "Encode a series of integers, producing a series of bytes. -> arbitrary quintuple byte"
65+
66+
[9be46731-7cd5-415c-b960-48061cbc1154]
67+
description = "Encode a series of integers, producing a series of bytes. -> asymmetric quintuple byte"
68+
69+
[7489694b-88c3-4078-9864-6fe802411009]
70+
description = "Encode a series of integers, producing a series of bytes. -> maximum 32-bit integer input"
71+
72+
[f9b91821-cada-4a73-9421-3c81d6ff3661]
73+
description = "Encode a series of integers, producing a series of bytes. -> two single-byte values"
74+
75+
[68694449-25d2-4974-ba75-fa7bb36db212]
76+
description = "Encode a series of integers, producing a series of bytes. -> two multi-byte values"
77+
78+
[51a06b5c-de1b-4487-9a50-9db1b8930d85]
79+
description = "Encode a series of integers, producing a series of bytes. -> many multi-byte values"
80+
81+
[baa73993-4514-4915-bac0-f7f585e0e59a]
82+
description = "Decode a series of bytes, producing a series of integers. -> one byte"
83+
84+
[72e94369-29f9-46f2-8c95-6c5b7a595aee]
85+
description = "Decode a series of bytes, producing a series of integers. -> two bytes"
86+
87+
[df5a44c4-56f7-464e-a997-1db5f63ce691]
88+
description = "Decode a series of bytes, producing a series of integers. -> three bytes"
89+
90+
[1bb58684-f2dc-450a-8406-1f3452aa1947]
91+
description = "Decode a series of bytes, producing a series of integers. -> four bytes"
92+
93+
[cecd5233-49f1-4dd1-a41a-9840a40f09cd]
94+
description = "Decode a series of bytes, producing a series of integers. -> maximum 32-bit integer"
95+
96+
[e7d74ba3-8b8e-4bcb-858d-d08302e15695]
97+
description = "Decode a series of bytes, producing a series of integers. -> incomplete sequence causes error"
98+
99+
[aa378291-9043-4724-bc53-aca1b4a3fcb6]
100+
description = "Decode a series of bytes, producing a series of integers. -> incomplete sequence causes error, even if value is zero"
101+
102+
[a91e6f5a-c64a-48e3-8a75-ce1a81e0ebee]
103+
description = "Decode a series of bytes, producing a series of integers. -> multiple values"
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
encode: (numbers) ->
3+
error 'Implement the encode function'
4+
5+
decode: (bytes) ->
6+
error 'Implement the decode function'
7+
}
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
VariableLengthQuantity = require 'variable_length_quantity'
2+
3+
describe 'variable-length-quantity', ->
4+
describe 'Encode a series of integers, producing a series of bytes.', ->
5+
it 'zero', ->
6+
result = VariableLengthQuantity.encode {0}
7+
expected = {0}
8+
assert.are.same expected, result
9+
10+
pending 'arbitrary single byte', ->
11+
result = VariableLengthQuantity.encode {64}
12+
expected = {64}
13+
assert.are.same expected, result
14+
15+
pending 'asymmetric single byte', ->
16+
result = VariableLengthQuantity.encode {83}
17+
expected = {83}
18+
assert.are.same expected, result
19+
20+
pending 'largest single byte', ->
21+
result = VariableLengthQuantity.encode {127}
22+
expected = {127}
23+
assert.are.same expected, result
24+
25+
pending 'smallest double byte', ->
26+
result = VariableLengthQuantity.encode {128}
27+
expected = {129, 0}
28+
assert.are.same expected, result
29+
30+
pending 'arbitrary double byte', ->
31+
result = VariableLengthQuantity.encode {8192}
32+
expected = {192, 0}
33+
assert.are.same expected, result
34+
35+
pending 'asymmetric double byte', ->
36+
result = VariableLengthQuantity.encode {173}
37+
expected = {129, 45}
38+
assert.are.same expected, result
39+
40+
pending 'largest double byte', ->
41+
result = VariableLengthQuantity.encode {16383}
42+
expected = {255, 127}
43+
assert.are.same expected, result
44+
45+
pending 'smallest triple byte', ->
46+
result = VariableLengthQuantity.encode {16384}
47+
expected = {129, 128, 0}
48+
assert.are.same expected, result
49+
50+
pending 'arbitrary triple byte', ->
51+
result = VariableLengthQuantity.encode {1048576}
52+
expected = {192, 128, 0}
53+
assert.are.same expected, result
54+
55+
pending 'asymmetric triple byte', ->
56+
result = VariableLengthQuantity.encode {120220}
57+
expected = {135, 171, 28}
58+
assert.are.same expected, result
59+
60+
pending 'largest triple byte', ->
61+
result = VariableLengthQuantity.encode {2097151}
62+
expected = {255, 255, 127}
63+
assert.are.same expected, result
64+
65+
pending 'smallest quadruple byte', ->
66+
result = VariableLengthQuantity.encode {2097152}
67+
expected = {129, 128, 128, 0}
68+
assert.are.same expected, result
69+
70+
pending 'arbitrary quadruple byte', ->
71+
result = VariableLengthQuantity.encode {134217728}
72+
expected = {192, 128, 128, 0}
73+
assert.are.same expected, result
74+
75+
pending 'asymmetric quadruple byte', ->
76+
result = VariableLengthQuantity.encode {3503876}
77+
expected = {129, 213, 238, 4}
78+
assert.are.same expected, result
79+
80+
pending 'largest quadruple byte', ->
81+
result = VariableLengthQuantity.encode {268435455}
82+
expected = {255, 255, 255, 127}
83+
assert.are.same expected, result
84+
85+
pending 'smallest quintuple byte', ->
86+
result = VariableLengthQuantity.encode {268435456}
87+
expected = {129, 128, 128, 128, 0}
88+
assert.are.same expected, result
89+
90+
pending 'arbitrary quintuple byte', ->
91+
result = VariableLengthQuantity.encode {4278190080}
92+
expected = {143, 248, 128, 128, 0}
93+
assert.are.same expected, result
94+
95+
pending 'asymmetric quintuple byte', ->
96+
result = VariableLengthQuantity.encode {2254790917}
97+
expected = {136, 179, 149, 194, 5}
98+
assert.are.same expected, result
99+
100+
pending 'maximum 32-bit integer input', ->
101+
result = VariableLengthQuantity.encode {4294967295}
102+
expected = {143, 255, 255, 255, 127}
103+
assert.are.same expected, result
104+
105+
pending 'two single-byte values', ->
106+
result = VariableLengthQuantity.encode {64, 127}
107+
expected = {64, 127}
108+
assert.are.same expected, result
109+
110+
pending 'two multi-byte values', ->
111+
result = VariableLengthQuantity.encode {16384, 1193046}
112+
expected = {129, 128, 0, 200, 232, 86}
113+
assert.are.same expected, result
114+
115+
pending 'many multi-byte values', ->
116+
result = VariableLengthQuantity.encode {8192, 1193046, 268435455, 0, 16383, 16384}
117+
expected = {192, 0, 200, 232, 86, 255, 255, 255, 127, 0, 255, 127, 129, 128, 0}
118+
assert.are.same expected, result
119+
120+
describe 'Decode a series of bytes, producing a series of integers.', ->
121+
pending 'one byte', ->
122+
result = VariableLengthQuantity.decode {127}
123+
expected = {127}
124+
assert.are.same expected, result
125+
126+
pending 'two bytes', ->
127+
result = VariableLengthQuantity.decode {192, 0}
128+
expected = {8192}
129+
assert.are.same expected, result
130+
131+
pending 'three bytes', ->
132+
result = VariableLengthQuantity.decode {255, 255, 127}
133+
expected = {2097151}
134+
assert.are.same expected, result
135+
136+
pending 'four bytes', ->
137+
result = VariableLengthQuantity.decode {129, 128, 128, 0}
138+
expected = {2097152}
139+
assert.are.same expected, result
140+
141+
pending 'maximum 32-bit integer', ->
142+
result = VariableLengthQuantity.decode {143, 255, 255, 255, 127}
143+
expected = {4294967295}
144+
assert.are.same expected, result
145+
146+
pending 'incomplete sequence causes error', ->
147+
f = -> VariableLengthQuantity.decode {255}
148+
assert.has.errors f, 'incomplete sequence'
149+
150+
pending 'incomplete sequence causes error, even if value is zero', ->
151+
f = -> VariableLengthQuantity.decode {128}
152+
assert.has.errors f, 'incomplete sequence'
153+
154+
pending 'multiple values', ->
155+
result = VariableLengthQuantity.decode {192, 0, 200, 232, 86, 255, 255, 255, 127, 0, 255, 127, 129, 128, 0}
156+
expected = {8192, 1193046, 268435455, 0, 16383, 16384}
157+
assert.are.same expected, result

0 commit comments

Comments
 (0)