diff --git a/bin/generate-spec b/bin/generate-spec index a867b08..21bab13 100755 --- a/bin/generate-spec +++ b/bin/generate-spec @@ -89,9 +89,9 @@ process = (node, level=0) -> output = {} if node.description - table.insert output, indent("describe #{quote node.description}, ->", level) + table.insert output, indent("describe #{quote(node.description .. ':')}, ->", level) else - table.insert output, indent("describe '#{exercise_name}', ->", level) + table.insert output, indent("describe '#{exercise_name}:', ->", level) if spec_generator.test_helpers table.insert output, spec_generator.test_helpers diff --git a/config.json b/config.json index 538e0b6..f7423bf 100644 --- a/config.json +++ b/config.json @@ -834,6 +834,14 @@ "prerequisites": [], "difficulty": 6 }, + { + "slug": "intergalactic-transmission", + "name": "Intergalactic Transmission", + "uuid": "1cc8a4c8-84b7-4a5a-aa4c-7e9813a91d41", + "practices": [], + "prerequisites": [], + "difficulty": 6 + }, { "slug": "palindrome-products", "name": "Palindrome Products", diff --git a/exercises/practice/intergalactic-transmission/.busted b/exercises/practice/intergalactic-transmission/.busted new file mode 100644 index 0000000..86b84e7 --- /dev/null +++ b/exercises/practice/intergalactic-transmission/.busted @@ -0,0 +1,5 @@ +return { + default = { + ROOT = { '.' } + } +} diff --git a/exercises/practice/intergalactic-transmission/.docs/instructions.md b/exercises/practice/intergalactic-transmission/.docs/instructions.md new file mode 100644 index 0000000..5497088 --- /dev/null +++ b/exercises/practice/intergalactic-transmission/.docs/instructions.md @@ -0,0 +1,54 @@ +# Instructions + +Your job is to help implement + +- the transmitter, which calculates the transmission sequence, and +- the receiver, which decodes it. + +A parity bit is simple way of detecting transmission errors. +The transmitters and receivers can only transmit and receive _exactly_ eight bits at a time (including the parity bit). +The parity bit is set so that there is an _even_ number of 1 bits in each transmission, and the parity bit is always the first bit from the right. +So if the receiver receives `11000001`, `01110101` or `01000000` (i.e. a transmission with an odd number of 1 bits), it knows there is an error. + +However, messages are rarely this short, and need to be transmitted in a sequence when they are longer. + +For example, consider the message `11000000 00000001 11000000 11011110` (or `C0 01 C0 DE` in hex). + +Since each transmission contains exactly eight bits, it can only contain seven bits of data and the parity bit. +A parity bit must then be inserted after every seven bits of data: + +```text +11000000 00000001 11000000 11011110 + ↑ ↑ ↑ ↑ (7th bits) +``` + +The transmission sequence for this message looks like this: + +```text +1100000_ 0000000_ 0111000_ 0001101_ 1110 + ↑ ↑ ↑ ↑ (parity bits) +``` + +The data in the first transmission in the sequence (`1100000`) has two 1 bits (an even number), so the parity bit is 0. +The first transmission becomes `11000000` (or `C0` in hex). + +The data in the next transmission (`0000000`) has zero 1 bits (an even number again), so the parity bit is 0 again. +The second transmission thus becomes `00000000` (or `00` in hex). + +The data for the next two transmissions (`0111000` and `0001101`) have three 1 bits. +Their parity bits are set to 1 so that they have an even number of 1 bits in the transmission. +They are transmitted as `01110001` and `00011011` (or `71` and `1B` in hex). + +The last transmission (`1110`) has only four bits of data. +Since exactly eight bits are transmitted at a time and the parity bit is the rightmost bit, three 0 bits and then the parity bit are added to make up eight bits. +It now looks like this (where `_` is the parity bit): + +```text +1110 000_ + ↑↑↑ (added 0 bits) +``` + +There is an odd number of 1 bits again, so the parity bit is 1. +The last transmission in the sequence becomes `11100001` (or `E1` in hex). + +The entire transmission sequence for this message is `11000000 00000000 01110001 00011011 11100001` (or `C0 00 71 1B E1` in hex). diff --git a/exercises/practice/intergalactic-transmission/.docs/introduction.md b/exercises/practice/intergalactic-transmission/.docs/introduction.md new file mode 100644 index 0000000..f19dffb --- /dev/null +++ b/exercises/practice/intergalactic-transmission/.docs/introduction.md @@ -0,0 +1,23 @@ +# Introduction + +Trillions upon trillions of messages zip between Earth and neighboring galaxies every millisecond. +But transmitting over such long distances is tricky. +Pesky solar flares, temporal distortions, stray forces, and even the flap of a space butterfly's wing can cause a random bit to change during transmission. + +Now imagine the consequences: + +- Crashing the Intergalactic Share Market when "buy low" turns to "sell now". +- Losing contact with the Kepler Whirl system when "save new worm hole" becomes "cave new worm hole". +- Or plunging the universe into existential horror by replacing a cowboy emoji 🤠 with a clown emoji 🤡. + +Detecting corrupted messages isn't just important — it's critical. +The receiver _must_ know when something has gone wrong before disaster strikes. + +But how? +Scientists and engineers from across the universe have been battling this problem for eons. +Entire cosmic AI superclusters churn through the data. +And then, one day, a legend resurfaces — an ancient, powerful method, whispered in debugging forums, muttered by engineers who've seen too much... + +The Parity Bit! + +A method so simple, so powerful, that it might just save interstellar communication. diff --git a/exercises/practice/intergalactic-transmission/.meta/config.json b/exercises/practice/intergalactic-transmission/.meta/config.json new file mode 100644 index 0000000..3b29af5 --- /dev/null +++ b/exercises/practice/intergalactic-transmission/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "glennj" + ], + "files": { + "solution": [ + "intergalactic_transmission.moon" + ], + "test": [ + "intergalactic_transmission_spec.moon" + ], + "example": [ + ".meta/example.moon" + ] + }, + "blurb": "Add parity bits to a message for transmission", + "source": "Kah Goh", + "source_url": "https://github.com/exercism/problem-specifications/pull/2543" +} diff --git a/exercises/practice/intergalactic-transmission/.meta/example.moon b/exercises/practice/intergalactic-transmission/.meta/example.moon new file mode 100644 index 0000000..e576925 --- /dev/null +++ b/exercises/practice/intergalactic-transmission/.meta/example.moon @@ -0,0 +1,55 @@ +import fold from require 'moon' +BitList = require 'pl.List' + +BitList.take = (self, i) -> self\slice 1, i +BitList.drop = (self, i) -> self\slice i + 1 +BitList.parity = (self) -> fold({0, table.unpack self}, (c, b) -> c + b) & 1 +BitList.addParityBit = (self) -> self\append self\parity! +BitList.todec = (self) -> fold {0, table.unpack self}, (n, b) -> n << 1 | b +BitList.tohex = (self) -> string.format '0x%02x', self\todec! +BitList.pad = (self, wid, elem) -> + while #self < wid do self\append elem + self + +tobits = (bytes) -> + bytes = [tonumber b\sub(3), 16 for b in *bytes] + allBits = BitList! + for byte in *bytes + bits = BitList! + for _ = 1, 8 + bits\insert 1, byte & 1 + byte >>= 1 + allBits\extend bits + allBits + + +{ + transmitSequence: (sequence) -> + seqBits = tobits sequence + + message = BitList! + while #seqBits >= 7 + message\append seqBits\take(7)\addParityBit!\tohex! + seqBits = seqBits\drop 7 + + if #seqBits > 0 + message\append seqBits\pad(7, 0)\addParityBit!\tohex! + message + + + decodeMessage: (message) -> + msgBits = tobits message + + seqBits = BitList! + while #msgBits >= 8 + sevenBits = msgBits\take 7 + assert msgBits[8] == sevenBits\parity!, 'wrong parity' + seqBits\extend sevenBits + msgBits = msgBits\drop 8 + + sequence = BitList! + while #seqBits >= 8 + sequence\append seqBits\take(8)\tohex! + seqBits = seqBits\drop 8 + sequence +} diff --git a/exercises/practice/intergalactic-transmission/.meta/spec_generator.moon b/exercises/practice/intergalactic-transmission/.meta/spec_generator.moon new file mode 100644 index 0000000..8649640 --- /dev/null +++ b/exercises/practice/intergalactic-transmission/.meta/spec_generator.moon @@ -0,0 +1,24 @@ +import string_list from require 'test_helpers' + +{ + module_imports: {'transmitSequence', 'decodeMessage'}, + + test_helpers: [[ + -- Inputs and expected data are given in hexadecimal. +]] + + generate_test: (case, level) -> + lines = if case.expected.error + { + "f = -> #{case.property} #{string_list case.input.message, level, inline: 7}", + "assert.has.error f, #{quote case.expected.error}" + } + else + { + "result = #{case.property} #{string_list case.input.message, level, inline: 7}", + "expected = #{string_list case.expected, level, inline: 7}", + "assert.are.same expected, result" + } + + table.concat [indent line, level for line in *lines], '\n' +} diff --git a/exercises/practice/intergalactic-transmission/.meta/tests.toml b/exercises/practice/intergalactic-transmission/.meta/tests.toml new file mode 100644 index 0000000..64a8aac --- /dev/null +++ b/exercises/practice/intergalactic-transmission/.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. + +[f99d4046-b429-4582-9324-f0bcac7ab51c] +description = "calculate transmit sequences -> empty message" + +[ee27ea2d-8999-4f23-9275-8f6879545f86] +description = "calculate transmit sequences -> 0x00 is transmitted as 0x0000" + +[97f27f98-8020-402d-be85-f21ba54a6df0] +description = "calculate transmit sequences -> 0x02 is transmitted as 0x0300" + +[24712fb9-0336-4e2f-835e-d2350f29c420] +description = "calculate transmit sequences -> 0x06 is transmitted as 0x0600" + +[7630b5a9-dba1-4178-b2a0-4a376f7414e0] +description = "calculate transmit sequences -> 0x05 is transmitted as 0x0581" + +[ab4fe80b-ef8e-4a99-b4fb-001937af415d] +description = "calculate transmit sequences -> 0x29 is transmitted as 0x2881" + +[4e200d84-593b-4449-b7c0-4de1b6a0955e] +description = "calculate transmit sequences -> 0xc001c0de is transmitted as 0xc000711be1" + +[fbc537e9-6b21-4f4a-8c2b-9cf9b702a9b7] +description = "calculate transmit sequences -> six byte message" + +[d5b75adf-b5fc-4f77-b4ab-77653e30f07c] +description = "calculate transmit sequences -> seven byte message" + +[6d8b297b-da1d-435e-bcd7-55fbb1400e73] +description = "calculate transmit sequences -> eight byte message" + +[54a0642a-d5aa-490c-be89-8e171a0cab6f] +description = "calculate transmit sequences -> twenty byte message" + +[9a8084dd-3336-474c-90cb-8a852524604d] +description = "decode received messages -> empty message" + +[879af739-0094-4736-9127-bd441b1ddbbf] +description = "decode received messages -> zero message" + +[7a89eeef-96c5-4329-a246-ec181a8e959a] +description = "decode received messages -> 0x0300 is decoded to 0x02" + +[3e515af7-8b62-417f-960c-3454bca7f806] +description = "decode received messages -> 0x0581 is decoded to 0x05" + +[a1b4a3f7-9f05-4b7a-b86e-d7c6fc3f16a9] +description = "decode received messages -> 0x2881 is decoded to 0x29" + +[2e99d617-4c91-4ad5-9217-e4b2447d6e4a] +description = "decode received messages -> first byte has wrong parity" + +[507e212d-3dae-42e8-88b4-2223838ff8d2] +description = "decode received messages -> second byte has wrong parity" + +[b985692e-6338-46c7-8cea-bc38996d4dfd] +description = "decode received messages -> 0xcf4b00 is decoded to 0xce94" + +[7a1f4d48-696d-4679-917c-21b7da3ff3fd] +description = "decode received messages -> 0xe2566500 is decoded to 0xe2ad90" + +[467549dc-a558-443b-80c5-ff3d4eb305d4] +description = "decode received messages -> six byte message" + +[1f3be5fb-093a-4661-9951-c1c4781c71ea] +description = "decode received messages -> seven byte message" + +[6065b8b3-9dcd-45c9-918c-b427cfdb28c1] +description = "decode received messages -> last byte has wrong parity" + +[98af97b7-9cca-4c4c-9de3-f70e227a4cb1] +description = "decode received messages -> eight byte message" + +[aa7d4785-2bb9-43a4-a38a-203325c464fb] +description = "decode received messages -> twenty byte message" + +[4c86e034-b066-42ac-8497-48f9bc1723c1] +description = "decode received messages -> wrong parity on 16th byte" diff --git a/exercises/practice/intergalactic-transmission/intergalactic_transmission.moon b/exercises/practice/intergalactic-transmission/intergalactic_transmission.moon new file mode 100644 index 0000000..a54e2f6 --- /dev/null +++ b/exercises/practice/intergalactic-transmission/intergalactic_transmission.moon @@ -0,0 +1,7 @@ +{ + transmitSequence: (sequence) -> + error 'Implement me' + + decodeMessage: (message) -> + error 'Implement me' +} diff --git a/exercises/practice/intergalactic-transmission/intergalactic_transmission_spec.moon b/exercises/practice/intergalactic-transmission/intergalactic_transmission_spec.moon new file mode 100644 index 0000000..e572c63 --- /dev/null +++ b/exercises/practice/intergalactic-transmission/intergalactic_transmission_spec.moon @@ -0,0 +1,313 @@ +import transmitSequence, decodeMessage from require 'intergalactic_transmission' + +describe 'intergalactic-transmission:', -> + -- Inputs and expected data are given in hexadecimal. + + describe 'calculate transmit sequences:', -> + it 'empty message', -> + result = transmitSequence {} + expected = {} + assert.are.same expected, result + + pending '0x00 is transmitted as 0x0000', -> + result = transmitSequence {'0x00'} + expected = {'0x00', '0x00'} + assert.are.same expected, result + + pending '0x02 is transmitted as 0x0300', -> + result = transmitSequence {'0x02'} + expected = {'0x03', '0x00'} + assert.are.same expected, result + + pending '0x06 is transmitted as 0x0600', -> + result = transmitSequence {'0x06'} + expected = {'0x06', '0x00'} + assert.are.same expected, result + + pending '0x05 is transmitted as 0x0581', -> + result = transmitSequence {'0x05'} + expected = {'0x05', '0x81'} + assert.are.same expected, result + + pending '0x29 is transmitted as 0x2881', -> + result = transmitSequence {'0x29'} + expected = {'0x28', '0x81'} + assert.are.same expected, result + + pending '0xc001c0de is transmitted as 0xc000711be1', -> + result = transmitSequence {'0xc0', '0x01', '0xc0', '0xde'} + expected = {'0xc0', '0x00', '0x71', '0x1b', '0xe1'} + assert.are.same expected, result + + pending 'six byte message', -> + result = transmitSequence {'0x47', '0x72', '0x65', '0x61', '0x74', '0x21'} + expected = {'0x47', '0xb8', '0x99', '0xac', '0x17', '0xa0', '0x84'} + assert.are.same expected, result + + pending 'seven byte message', -> + result = transmitSequence {'0x47', '0x72', '0x65', '0x61', '0x74', '0x31', '0x21'} + expected = { + '0x47', + '0xb8', + '0x99', + '0xac', + '0x17', + '0xa0', + '0xc5', + '0x42', + } + assert.are.same expected, result + + pending 'eight byte message', -> + result = transmitSequence { + '0xc0', + '0x01', + '0x13', + '0x37', + '0xc0', + '0xde', + '0x21', + '0x21', + } + expected = { + '0xc0', + '0x00', + '0x44', + '0x66', + '0x7d', + '0x06', + '0x78', + '0x42', + '0x21', + '0x81', + } + assert.are.same expected, result + + pending 'twenty byte message', -> + result = transmitSequence { + '0x45', + '0x78', + '0x65', + '0x72', + '0x63', + '0x69', + '0x73', + '0x6d', + '0x20', + '0x69', + '0x73', + '0x20', + '0x61', + '0x77', + '0x65', + '0x73', + '0x6f', + '0x6d', + '0x65', + '0x21', + } + expected = { + '0x44', + '0xbd', + '0x18', + '0xaf', + '0x27', + '0x1b', + '0xa5', + '0xe7', + '0x6c', + '0x90', + '0x1b', + '0x2e', + '0x33', + '0x03', + '0x84', + '0xee', + '0x65', + '0xb8', + '0xdb', + '0xed', + '0xd7', + '0x28', + '0x84', + } + assert.are.same expected, result + + describe 'decode received messages:', -> + pending 'empty message', -> + result = decodeMessage {} + expected = {} + assert.are.same expected, result + + pending 'zero message', -> + result = decodeMessage {'0x00', '0x00'} + expected = {'0x00'} + assert.are.same expected, result + + pending '0x0300 is decoded to 0x02', -> + result = decodeMessage {'0x03', '0x00'} + expected = {'0x02'} + assert.are.same expected, result + + pending '0x0581 is decoded to 0x05', -> + result = decodeMessage {'0x05', '0x81'} + expected = {'0x05'} + assert.are.same expected, result + + pending '0x2881 is decoded to 0x29', -> + result = decodeMessage {'0x28', '0x81'} + expected = {'0x29'} + assert.are.same expected, result + + pending 'first byte has wrong parity', -> + f = -> decodeMessage {'0x07', '0x00'} + assert.has.error f, 'wrong parity' + + pending 'second byte has wrong parity', -> + f = -> decodeMessage {'0x03', '0x68'} + assert.has.error f, 'wrong parity' + + pending '0xcf4b00 is decoded to 0xce94', -> + result = decodeMessage {'0xcf', '0x4b', '0x00'} + expected = {'0xce', '0x94'} + assert.are.same expected, result + + pending '0xe2566500 is decoded to 0xe2ad90', -> + result = decodeMessage {'0xe2', '0x56', '0x65', '0x00'} + expected = {'0xe2', '0xad', '0x90'} + assert.are.same expected, result + + pending 'six byte message', -> + result = decodeMessage {'0x47', '0xb8', '0x99', '0xac', '0x17', '0xa0', '0x84'} + expected = {'0x47', '0x72', '0x65', '0x61', '0x74', '0x21'} + assert.are.same expected, result + + pending 'seven byte message', -> + result = decodeMessage { + '0x47', + '0xb8', + '0x99', + '0xac', + '0x17', + '0xa0', + '0xc5', + '0x42', + } + expected = {'0x47', '0x72', '0x65', '0x61', '0x74', '0x31', '0x21'} + assert.are.same expected, result + + pending 'last byte has wrong parity', -> + f = -> decodeMessage { + '0x47', + '0xb8', + '0x99', + '0xac', + '0x17', + '0xa0', + '0xc5', + '0x43', + } + assert.has.error f, 'wrong parity' + + pending 'eight byte message', -> + result = decodeMessage { + '0xc0', + '0x00', + '0x44', + '0x66', + '0x7d', + '0x06', + '0x78', + '0x42', + '0x21', + '0x81', + } + expected = { + '0xc0', + '0x01', + '0x13', + '0x37', + '0xc0', + '0xde', + '0x21', + '0x21', + } + assert.are.same expected, result + + pending 'twenty byte message', -> + result = decodeMessage { + '0x44', + '0xbd', + '0x18', + '0xaf', + '0x27', + '0x1b', + '0xa5', + '0xe7', + '0x6c', + '0x90', + '0x1b', + '0x2e', + '0x33', + '0x03', + '0x84', + '0xee', + '0x65', + '0xb8', + '0xdb', + '0xed', + '0xd7', + '0x28', + '0x84', + } + expected = { + '0x45', + '0x78', + '0x65', + '0x72', + '0x63', + '0x69', + '0x73', + '0x6d', + '0x20', + '0x69', + '0x73', + '0x20', + '0x61', + '0x77', + '0x65', + '0x73', + '0x6f', + '0x6d', + '0x65', + '0x21', + } + assert.are.same expected, result + + pending 'wrong parity on 16th byte', -> + f = -> decodeMessage { + '0x44', + '0xbd', + '0x18', + '0xaf', + '0x27', + '0x1b', + '0xa5', + '0xe7', + '0x6c', + '0x90', + '0x1b', + '0x2e', + '0x33', + '0x03', + '0x84', + '0xef', + '0x65', + '0xb8', + '0xdb', + '0xed', + '0xd7', + '0x28', + '0x84', + } + assert.has.error f, 'wrong parity'