diff --git a/config.json b/config.json index 5a064231..26523435 100644 --- a/config.json +++ b/config.json @@ -679,6 +679,27 @@ "strings" ] }, + { + "slug": "baffling-birthdays", + "name": "Baffling Birthdays", + "uuid": "54dd8d8f-b58c-47d0-8c77-22dd5fcdf14f", + "practices": [], + "prerequisites": [], + "difficulty": 3 + }, + { + "slug": "split-second-stopwatch", + "name": "Split-Second Stopwatch", + "uuid": "49a0daf5-ecf8-41ad-8517-f969b770bf32", + "practices": [], + "prerequisites": [], + "difficulty": 3, + "topics": [ + "strings", + "text-formatting", + "time" + ] + }, { "slug": "acronym", "name": "Acronym", @@ -1319,6 +1340,17 @@ "control_flow_loops" ] }, + { + "slug": "intergalactic-transmission", + "name": "Intergalactic Transmission", + "uuid": "de43c93f-f2be-42f8-a883-4b13ee336ccb", + "practices": [], + "prerequisites": [], + "difficulty": 6, + "topics": [ + "bitwise_operations" + ] + }, { "slug": "flower-field", "name": "Flower Field", @@ -1481,27 +1513,6 @@ "exception_handling", "graphs" ] - }, - { - "slug": "baffling-birthdays", - "name": "Baffling Birthdays", - "uuid": "54dd8d8f-b58c-47d0-8c77-22dd5fcdf14f", - "practices": [], - "prerequisites": [], - "difficulty": 3 - }, - { - "slug": "split-second-stopwatch", - "name": "Split-Second Stopwatch", - "uuid": "49a0daf5-ecf8-41ad-8517-f969b770bf32", - "practices": [], - "prerequisites": [], - "difficulty": 3, - "topics": [ - "strings", - "text-formatting", - "time" - ] } ] }, diff --git a/exercises/practice/intergalactic-transmission/.busted b/exercises/practice/intergalactic-transmission/.busted new file mode 100644 index 00000000..86b84e7c --- /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 00000000..54970881 --- /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 00000000..f19dffbe --- /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 00000000..22a7972d --- /dev/null +++ b/exercises/practice/intergalactic-transmission/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "ryanplusplus" + ], + "files": { + "solution": [ + "intergalactic-transmission.lua" + ], + "test": [ + "intergalactic-transmission_spec.lua" + ], + "example": [ + ".meta/example.lua" + ] + }, + "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.lua b/exercises/practice/intergalactic-transmission/.meta/example.lua new file mode 100644 index 00000000..f3ee3afc --- /dev/null +++ b/exercises/practice/intergalactic-transmission/.meta/example.lua @@ -0,0 +1,64 @@ +local function read_bit(bytes, index) + local byte_index = math.ceil(index / 8) + local bit_index = (index - 1) % 8 + if byte_index > #bytes then + return nil + end + return (bytes[byte_index] >> (7 - bit_index)) & 1 +end + +local function bit_chunks(bytes, chunk_size) + return coroutine.wrap(function() + for i = 1, #bytes * 8, chunk_size do + local chunk = 0 + for j = 0, chunk_size - 1 do + local bit = read_bit(bytes, i + j) + chunk = (chunk << 1) | (bit or 0) + end + coroutine.yield(chunk) + end + end) +end + +local function popcount(byte) + local count = 0 + while byte > 0 do + count = count + byte & 1 + byte = byte >> 1 + end + return count +end + +local function transmit_sequence(sequence) + local result = {} + for chunk in bit_chunks(sequence, 7) do + table.insert(result, (chunk << 1) | popcount(chunk) % 2) + end + return result +end + +local function decode_message(message) + local result = {} + local accumulator = 0 + local accumulator_count = 0 + + for _, byte in ipairs(message) do + assert(popcount(byte) % 2 == 0, 'wrong parity') + + for i = 7, 1, -1 do + local bit = (byte >> i) & 1 + accumulator = (accumulator << 1) | bit + accumulator_count = accumulator_count + 1 + + if accumulator_count == 8 then + table.insert(result, accumulator) + accumulator = 0 + accumulator_count = 0 + end + end + end + + return result +end + +return { transmit_sequence = transmit_sequence, decode_message = decode_message } diff --git a/exercises/practice/intergalactic-transmission/.meta/spec_generator.lua b/exercises/practice/intergalactic-transmission/.meta/spec_generator.lua new file mode 100644 index 00000000..fb954593 --- /dev/null +++ b/exercises/practice/intergalactic-transmission/.meta/spec_generator.lua @@ -0,0 +1,21 @@ +local utils = require 'utils' + +return { + module_name = 'IntergalacticTransmission', + + generate_test = function(case) + if case.expected.error then + local template = [[ + assert.has_error(function() IntergalacticTransmission.%s({ %s }) end, %s)]] + return template:format(utils.snake_case(case.property), table.concat(case.input.message, ', --\n'), + utils.stringify(case.expected.error)) + else + local template = [[ + local expected = { %s } + local actual = IntergalacticTransmission.%s({ %s }) + assert.are.same(expected, actual)]] + return template:format(table.concat(case.expected, ', --\n'), utils.snake_case(case.property), + table.concat(case.input.message, ', --\n')) + end + end +} diff --git a/exercises/practice/intergalactic-transmission/.meta/tests.toml b/exercises/practice/intergalactic-transmission/.meta/tests.toml new file mode 100644 index 00000000..64a8aace --- /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.lua b/exercises/practice/intergalactic-transmission/intergalactic-transmission.lua new file mode 100644 index 00000000..d0d5978f --- /dev/null +++ b/exercises/practice/intergalactic-transmission/intergalactic-transmission.lua @@ -0,0 +1,7 @@ +local function transmit_sequence(sequence) +end + +local function decode_message(message) +end + +return { transmit_sequence = transmit_sequence, decode_message = decode_message } diff --git a/exercises/practice/intergalactic-transmission/intergalactic-transmission_spec.lua b/exercises/practice/intergalactic-transmission/intergalactic-transmission_spec.lua new file mode 100644 index 00000000..7291f567 --- /dev/null +++ b/exercises/practice/intergalactic-transmission/intergalactic-transmission_spec.lua @@ -0,0 +1,450 @@ +local IntergalacticTransmission = require('intergalactic-transmission') + +describe('intergalactic-transmission', function() + describe('calculate transmit sequences', function() + it('empty message', function() + local expected = {} + local actual = IntergalacticTransmission.transmit_sequence({}) + assert.are.same(expected, actual) + end) + + it('0x00 is transmitted as 0x0000', function() + local expected = { + 0x00, -- + 0x00 + } + local actual = IntergalacticTransmission.transmit_sequence({ 0x00 }) + assert.are.same(expected, actual) + end) + + it('0x02 is transmitted as 0x0300', function() + local expected = { + 0x03, -- + 0x00 + } + local actual = IntergalacticTransmission.transmit_sequence({ 0x02 }) + assert.are.same(expected, actual) + end) + + it('0x06 is transmitted as 0x0600', function() + local expected = { + 0x06, -- + 0x00 + } + local actual = IntergalacticTransmission.transmit_sequence({ 0x06 }) + assert.are.same(expected, actual) + end) + + it('0x05 is transmitted as 0x0581', function() + local expected = { + 0x05, -- + 0x81 + } + local actual = IntergalacticTransmission.transmit_sequence({ 0x05 }) + assert.are.same(expected, actual) + end) + + it('0x29 is transmitted as 0x2881', function() + local expected = { + 0x28, -- + 0x81 + } + local actual = IntergalacticTransmission.transmit_sequence({ 0x29 }) + assert.are.same(expected, actual) + end) + + it('0xc001c0de is transmitted as 0xc000711be1', function() + local expected = { + 0xc0, -- + 0x00, -- + 0x71, -- + 0x1b, -- + 0xe1 + } + local actual = IntergalacticTransmission.transmit_sequence({ + 0xc0, -- + 0x01, -- + 0xc0, -- + 0xde + }) + assert.are.same(expected, actual) + end) + + it('six byte message', function() + local expected = { + 0x47, -- + 0xb8, -- + 0x99, -- + 0xac, -- + 0x17, -- + 0xa0, -- + 0x84 + } + local actual = IntergalacticTransmission.transmit_sequence({ + 0x47, -- + 0x72, -- + 0x65, -- + 0x61, -- + 0x74, -- + 0x21 + }) + assert.are.same(expected, actual) + end) + + it('seven byte message', function() + local expected = { + 0x47, -- + 0xb8, -- + 0x99, -- + 0xac, -- + 0x17, -- + 0xa0, -- + 0xc5, -- + 0x42 + } + local actual = IntergalacticTransmission.transmit_sequence({ + 0x47, -- + 0x72, -- + 0x65, -- + 0x61, -- + 0x74, -- + 0x31, -- + 0x21 + }) + assert.are.same(expected, actual) + end) + + it('eight byte message', function() + local expected = { + 0xc0, -- + 0x00, -- + 0x44, -- + 0x66, -- + 0x7d, -- + 0x06, -- + 0x78, -- + 0x42, -- + 0x21, -- + 0x81 + } + local actual = IntergalacticTransmission.transmit_sequence({ + 0xc0, -- + 0x01, -- + 0x13, -- + 0x37, -- + 0xc0, -- + 0xde, -- + 0x21, -- + 0x21 + }) + assert.are.same(expected, actual) + end) + + it('twenty byte message', function() + local expected = { + 0x44, -- + 0xbd, -- + 0x18, -- + 0xaf, -- + 0x27, -- + 0x1b, -- + 0xa5, -- + 0xe7, -- + 0x6c, -- + 0x90, -- + 0x1b, -- + 0x2e, -- + 0x33, -- + 0x03, -- + 0x84, -- + 0xee, -- + 0x65, -- + 0xb8, -- + 0xdb, -- + 0xed, -- + 0xd7, -- + 0x28, -- + 0x84 + } + local actual = IntergalacticTransmission.transmit_sequence({ + 0x45, -- + 0x78, -- + 0x65, -- + 0x72, -- + 0x63, -- + 0x69, -- + 0x73, -- + 0x6d, -- + 0x20, -- + 0x69, -- + 0x73, -- + 0x20, -- + 0x61, -- + 0x77, -- + 0x65, -- + 0x73, -- + 0x6f, -- + 0x6d, -- + 0x65, -- + 0x21 + }) + assert.are.same(expected, actual) + end) + end) + + describe('decode received messages', function() + it('empty message', function() + local expected = {} + local actual = IntergalacticTransmission.decode_message({}) + assert.are.same(expected, actual) + end) + + it('zero message', function() + local expected = { 0x00 } + local actual = IntergalacticTransmission.decode_message({ + 0x00, -- + 0x00 + }) + assert.are.same(expected, actual) + end) + + it('0x0300 is decoded to 0x02', function() + local expected = { 0x02 } + local actual = IntergalacticTransmission.decode_message({ + 0x03, -- + 0x00 + }) + assert.are.same(expected, actual) + end) + + it('0x0581 is decoded to 0x05', function() + local expected = { 0x05 } + local actual = IntergalacticTransmission.decode_message({ + 0x05, -- + 0x81 + }) + assert.are.same(expected, actual) + end) + + it('0x2881 is decoded to 0x29', function() + local expected = { 0x29 } + local actual = IntergalacticTransmission.decode_message({ + 0x28, -- + 0x81 + }) + assert.are.same(expected, actual) + end) + + it('first byte has wrong parity', function() + assert.has_error(function() + IntergalacticTransmission.decode_message({ + 0x07, -- + 0x00 + }) + end, 'wrong parity') + end) + + it('second byte has wrong parity', function() + assert.has_error(function() + IntergalacticTransmission.decode_message({ + 0x03, -- + 0x68 + }) + end, 'wrong parity') + end) + + it('0xcf4b00 is decoded to 0xce94', function() + local expected = { + 0xce, -- + 0x94 + } + local actual = IntergalacticTransmission.decode_message({ + 0xcf, -- + 0x4b, -- + 0x00 + }) + assert.are.same(expected, actual) + end) + + it('0xe2566500 is decoded to 0xe2ad90', function() + local expected = { + 0xe2, -- + 0xad, -- + 0x90 + } + local actual = IntergalacticTransmission.decode_message({ + 0xe2, -- + 0x56, -- + 0x65, -- + 0x00 + }) + assert.are.same(expected, actual) + end) + + it('six byte message', function() + local expected = { + 0x47, -- + 0x72, -- + 0x65, -- + 0x61, -- + 0x74, -- + 0x21 + } + local actual = IntergalacticTransmission.decode_message({ + 0x47, -- + 0xb8, -- + 0x99, -- + 0xac, -- + 0x17, -- + 0xa0, -- + 0x84 + }) + assert.are.same(expected, actual) + end) + + it('seven byte message', function() + local expected = { + 0x47, -- + 0x72, -- + 0x65, -- + 0x61, -- + 0x74, -- + 0x31, -- + 0x21 + } + local actual = IntergalacticTransmission.decode_message({ + 0x47, -- + 0xb8, -- + 0x99, -- + 0xac, -- + 0x17, -- + 0xa0, -- + 0xc5, -- + 0x42 + }) + assert.are.same(expected, actual) + end) + + it('last byte has wrong parity', function() + assert.has_error(function() + IntergalacticTransmission.decode_message({ + 0x47, -- + 0xb8, -- + 0x99, -- + 0xac, -- + 0x17, -- + 0xa0, -- + 0xc5, -- + 0x43 + }) + end, 'wrong parity') + end) + + it('eight byte message', function() + local expected = { + 0xc0, -- + 0x01, -- + 0x13, -- + 0x37, -- + 0xc0, -- + 0xde, -- + 0x21, -- + 0x21 + } + local actual = IntergalacticTransmission.decode_message({ + 0xc0, -- + 0x00, -- + 0x44, -- + 0x66, -- + 0x7d, -- + 0x06, -- + 0x78, -- + 0x42, -- + 0x21, -- + 0x81 + }) + assert.are.same(expected, actual) + end) + + it('twenty byte message', function() + local expected = { + 0x45, -- + 0x78, -- + 0x65, -- + 0x72, -- + 0x63, -- + 0x69, -- + 0x73, -- + 0x6d, -- + 0x20, -- + 0x69, -- + 0x73, -- + 0x20, -- + 0x61, -- + 0x77, -- + 0x65, -- + 0x73, -- + 0x6f, -- + 0x6d, -- + 0x65, -- + 0x21 + } + local actual = IntergalacticTransmission.decode_message({ + 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, actual) + end) + + it('wrong parity on 16th byte', function() + assert.has_error(function() + IntergalacticTransmission.decode_message({ + 0x44, -- + 0xbd, -- + 0x18, -- + 0xaf, -- + 0x27, -- + 0x1b, -- + 0xa5, -- + 0xe7, -- + 0x6c, -- + 0x90, -- + 0x1b, -- + 0x2e, -- + 0x33, -- + 0x03, -- + 0x84, -- + 0xef, -- + 0x65, -- + 0xb8, -- + 0xdb, -- + 0xed, -- + 0xd7, -- + 0x28, -- + 0x84 + }) + end, 'wrong parity') + end) + end) +end)