Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add variable-length-quantity exercise #427

Merged
merged 4 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion config.json
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,14 @@
"practices": [],
"prerequisites": [],
"difficulty": 5
},
{
"slug": "variable-length-quantity",
"name": "Variable Length Quantity",
"uuid": "5073fd82-3025-4370-ae93-37b661c17483",
"practices": [],
"prerequisites": [],
"difficulty": 6
}
]
},
Expand Down Expand Up @@ -956,4 +964,4 @@
"typing/strong",
"used_for/scripts"
]
}
}
34 changes: 34 additions & 0 deletions exercises/practice/variable-length-quantity/.docs/instructions.md
Original file line number Diff line number Diff line change
@@ -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
19 changes: 19 additions & 0 deletions exercises/practice/variable-length-quantity/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"authors": [
"kmarker1101"
],
"files": {
"solution": [
"variable-length-quantity.el"
],
"test": [
"variable-length-quantity-test.el"
],
"example": [
".meta/example.el"
]
},
"blurb": "Implement variable length quantity encoding and decoding.",
"source": "A poor Splice developer having to implement MIDI encoding/decoding.",
"source_url": "https://splice.com"
}
43 changes: 43 additions & 0 deletions exercises/practice/variable-length-quantity/.meta/example.el
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
;;; variable-length-quantity.el --- Variable Length Quantity (exercism) -*- lexical-binding: t; -*-

;;; Commentary:

;;; Code:


(define-error 'incomplete "incomplete sequence")


(defun encode (numbers)
"Encode a list of numbers into VLQ format."
(let ((encode-single (lambda (number)
(let ((byte-string (list (logand number 127)))) ; Initialize with the least significant byte
(setq number (lsh number -7)) ; Shift right before the loop
(while (> number 0)
(push (logior (logand number 127) 128) byte-string)
(setq number (lsh number -7)))
byte-string))))
(apply #'append (mapcar encode-single numbers))))


(defun decode (byte-string)
"Decode a byte string from VLQ format into numbers."
(let ((values '())
(number 0)
(incomplete t))
(dolist (byte byte-string)
(setq number (lsh number 7))
(setq number (logior number (logand byte 127)))
(if (= (logand byte 128) 0)
(progn
(setq values (cons number values))
(setq number 0)
(setq incomplete nil))
(setq incomplete t)))
(if incomplete
(signal 'incomplete 'nil)
(reverse values))))


(provide 'variable-length-quantity)
;;; variable-length-quantity.el ends here
88 changes: 88 additions & 0 deletions exercises/practice/variable-length-quantity/.meta/tests.toml
Original file line number Diff line number Diff line change
@@ -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"
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
;;; variable-length-quantity-test.el --- Variable Length Quantity (exercism) -*- lexical-binding: t; -*-

;;; Commentary:

;;; Code:


(load-file "variable-length-quantity.el")
(declare-function encode "variable-length-quantity.el" (integers))
(declare-function decode "variable-length-quantity.el" (integers))


(ert-deftest zero ()
(should (equal (encode '(0)) '(0))))


(ert-deftest arbitrary-single-byte ()
(should (equal (encode '(64)) '(64))))


(ert-deftest largest-single-byte ()
(should (equal (encode '(127)) '(127))))


(ert-deftest smallest-double-byte ()
(should (equal (encode '(128)) '(129 0))))


(ert-deftest arbitrary-double-byte ()
(should (equal (encode '(8192)) '(192 0))))


(ert-deftest largest-double-byte ()
(should (equal (encode '(16383)) '(255 127))))


(ert-deftest smallest-triple-byte ()
(should (equal (encode '(16384)) '(129 128 0))))


(ert-deftest arbitrary-triple-byte ()
(should (equal (encode '(1048576)) '(192 128 0))))


(ert-deftest largest-triple-byte ()
(should (equal (encode '(2097151)) '(255 255 127))))


(ert-deftest smallest-quadruple-byte ()
(should (equal (encode '(2097152)) '(129 128 128 0))))


(ert-deftest arbitrary-quadruple-byte ()
(should (equal (encode '(134217728)) '(192 128 128 0))))


(ert-deftest largest-quadruple-byte ()
(should (equal (encode '(268435455)) '(255 255 255 127))))


(ert-deftest smallest-quintuple-byte ()
(should (equal (encode '(268435456)) '(129 128 128 128 0))))


(ert-deftest arbitrary-quintuple-byte ()
(should (equal (encode '(4278190080)) '(143 248 128 128 0))))


(ert-deftest maximum-32-bit-integer-input ()
(should (equal (encode '(4294967295)) '(143 255 255 255 127))))


(ert-deftest two-single-byte-values ()
(should (equal (encode '(64 127)) '(64 127))))


(ert-deftest two-multi-byte-values ()
(should (equal (encode '(16384 1193046)) '(129 128 0 200 232 86))))


(ert-deftest many-multi-byte-values ()
(should (equal (encode '(8192 1193046 268435455 0 16383 16384)) '(192 0 200 232 86 255 255 255 127 0 255 127 129 128 0))))


(ert-deftest one-byte ()
(should (equal (decode '(127)) '(127))))


(ert-deftest two-bytes ()
(should (equal (decode '(192 0)) '(8192))))


(ert-deftest three-bytes ()
(should (equal (decode '(255 255 127)) '(2097151))))


(ert-deftest four-bytes ()
(should (equal (decode '(129 128 128 0)) '(2097152))))


(ert-deftest maximum-32-bit-integer ()
(should (equal (decode '(143 255 255 255 127)) '(4294967295))))


(ert-deftest incomplete-sequence-causes-error ()
kmarker1101 marked this conversation as resolved.
Show resolved Hide resolved
(should-error (decode '(255)) :type 'incomplete))


(ert-deftest incomplete-sequence-causes-error-even-if-value-is-zero ()
(should-error (decode '(128)) :type 'incomplete))


(ert-deftest multiple-values ()
(should (equal (decode '(192 0 200 232 86 255 255 255 127 0 255 127 129 128 0)) '(8192 1193046 268435455 0 16383 16384))))


(provide 'variable-length-quantity-test)
;;; variable-length-quantity-test.el ends here
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
;;; variable-length-quantity.el --- Variable Length Quantity (exercism) -*- lexical-binding: t; -*-

;;; Commentary:

;;; Code:


(define-error 'incomplete
(error "Delete this S-Expression and write your own implementation"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want the students to provide a specific error message then you can test for it with the following pattern:

(ert-deftest encode-with-a-not-coprime-to-m ()
(let ((error-data
(should-error
(encode "This is a test." '(("a" . 6) ("b" . 17))))))
(should
(string=
"a and m must be coprime." (error-message-string error-data)))))

But I don't think it's necessary to expect a specific error message. In the past we didn't define-error in the stub file so just delete Line 8-9.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I will change to use hex, mainly because the instructions examples use hex. Thanks

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Testing for specific errors started in #400 where it made sense to distinguish between the two errors. We did the same on Racket for that exercise even though generally we avoid testing for specific errors there because students often raise a contract violation (more idiomatic) than an error.

Here, there’s only one error so yeah we don’t need that specificity.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kind of like testing for a specific error type, imo it's a good habit to get into to signal specific errors instead of the generic error type. My comment referred to testing for a specific error message.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be good now I think



(defun encode (integers)
(error "Delete this S-Expression and write your own implementation"))


(defun decode (integers)
(error "Delete this S-Expression and write your own implementation"))


(provide 'variable-length-quantity)
;;; variable-length-quantity.el ends here