Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
8 changes: 8 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,14 @@
"prerequisites": [],
"difficulty": 9
},
{
"slug": "intergalactic-transmission",
"name": "Intergalactic Transmission",
"uuid": "1fc2f497-ddd8-4826-bffe-01023efe6bd7",
"practices": [],
"prerequisites": [],
"difficulty": 9
},
{
"slug": "zebra-puzzle",
"name": "Zebra Puzzle",
Expand Down
Original file line number Diff line number Diff line change
@@ -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).
Original file line number Diff line number Diff line change
@@ -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.
19 changes: 19 additions & 0 deletions exercises/practice/intergalactic-transmission/.meta/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"authors": [
"rmonnet"
],
"files": {
"solution": [
"intergalactic_transmission.odin"
],
"test": [
"intergalactic_transmission_test.odin"
],
"example": [
".meta/example.odin"
]
},
"blurb": "Add parity bits to a message for transmission",
"source": "Kah Goh",
"source_url": "https://github.com/exercism/problem-specifications/pull/2543"
}
160 changes: 160 additions & 0 deletions exercises/practice/intergalactic-transmission/.meta/example.odin
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package intergalactic_transmission

transmit_sequence :: proc(msg: []u8) -> (seq: []u8) {

if len(msg) == 0 { return nil }

buf: comm_buffer
for chunk in msg {
transmit_chunk(&buf, chunk)
}
// There may be some remaining bit in the buffer, flush them.
if buf.step != 0 {
transmit_overflow(&buf)
}
return buf.data[:]
}

decode_message :: proc(seq: []u8) -> (msg: []u8, okay: bool) {

if len(seq) == 0 { return nil, true }

buf: comm_buffer
for chunk in seq {
if !has_even_ones(chunk) {
// We got a chunk with invalid parity, abort!
delete(buf.data)
return nil, false
}
decode_chunk(&buf, chunk)
}
// Because the original message is a set of 8bit words,
// once we got through all of them, what's left in the overflow register
// can only be some zeros (the reminder of a last padded chunk) so there is no need
// to flush the receive buffer.
return buf.data[:], true
}

comm_buffer :: struct {
step: u8,
overflow: u8,
data: [dynamic]u8,
}

// Since we only use 7 bits out of each 8bit word, we get leftovers that
// we store in an overflow register. After the 1st word, the leftover is 1 bit,
// after the 2nd word, the leftover is 2 bits, ..., after each 7th word,
// we end up with enough data in the overflow register to send an extra 8bit
// chunk (including parity bit) herefore the need to flush the buffer every 7
// messages.
//
// For the 1st word, we use the leftmost 7 bits to send and keep the
// 1 rightmost bit in the overflow register.
// For the 2nd word of the message, we will use the 6 leftmost bits
// to build the next tr_chunk and add the 1 bit that was in the overflow
// register to the right of the 6bit block (that bit came first in the
// transmission). We will then be left with 2 bits to store in the
// overflow buffer.
// We repeat the process with, at each step, one less bit of the
// message word going into the chunk and one more bit going into
// the overflow register, until we are left with 7 bits in the
// overflow register, at which point we can emit two chunks
// and go back to the situation we were for the 1st message
// (empty overflow register)
// The tr_chunk_masks and tr_overflow_masks split the current word
// into the rigthmost part (going into the tr_chunk) to be sent
// and the leftmost part (going into the overflow register).
// At each step that split move by 1 bit to the left.
transmit_chunk :: proc(buf: ^comm_buffer, chunk: u8) {

// Use the leftmost (7 - seq)th bits of chunk and store in position (7-seq)..1.
tr_chunk := (chunk & tr_chunk_masks[buf.step]) >> buf.step
// Add seq-th bits of overflow at position 7..(7-seq+1)
if buf.step > 0 {
tr_chunk |= (buf.overflow << (8 - buf.step))
}
// Store the remaining rightmost bits in the overflow register.
buf.overflow = chunk & tr_overflow_masks[buf.step]
package_chunk_for_transmission(buf, tr_chunk)
buf.step += 1
// If we have 7 bits in the overflow, we should flush it.
if buf.step == 7 {
transmit_overflow(buf)
}

}

// Every seven steps (when the overflow register has 7 bits)
// and at the end of the message, we need to flush the
// overflow register and send what's in there.
transmit_overflow :: proc(buf: ^comm_buffer) {

last_chunk := buf.overflow << (8 - buf.step)
package_chunk_for_transmission(buf, last_chunk)
// Reset the sequence since we emptied the overflow register.
buf.step = 0
}

// Add the parity bit and put the chunk in the data buffer for transmission.
package_chunk_for_transmission :: proc(buf: ^comm_buffer, chunk: u8) {

tr_chunk := chunk
// Compute and set the parity bit.
if !has_even_ones(tr_chunk) {
tr_chunk |= 1
}
append(&buf.data, tr_chunk)
}

// Each chunk we receive has 7bits usable. We need to
// collect them in sets of 8bits.
// For the 1st word received, we store the 7 bits in the overflow
// register (in the 7 leftmost bits).
// For the 2nd word, we add the 1 leftmost bit to the
// content of the overflow (to the right of the register),
// queue the now full overflow register and then reuse it
// to store the 6 rightmost remaining bits of the 2nd word.
// For each successive word, we keep doing this but with
// one more bit going to complete the previous overflow
// and one less bit going to the new overflow, until there is
// no more bits to go in the new overflow. At which point
// we are back to the same situation as for the first word.
decode_chunk :: proc(buf: ^comm_buffer, chunk: u8) {

// Remove the parity bit
rc_chunk := (chunk & 0xFE) >> 1
if buf.step == 0 {
// Store the chunk in the top of the overflow register.
buf.overflow = rc_chunk << 1
} else {
// Store the rightmost bits at the bottom of the overflow register.
buf.overflow |= ((rc_chunk & rc_chunk_masks[buf.step - 1]) >> (7 - buf.step))
// We have a full overflow register, output it.
append(&buf.data, buf.overflow)
// Store the leftmost remaining bits at the top of the overflow register.
buf.overflow = (rc_chunk & rc_overflow_masks[buf.step - 1]) << (buf.step + 1)
}
buf.step = (buf.step + 1) % 8
}

// Check if the word has an even number of bits set to 1.
has_even_ones :: proc(n: u8) -> bool {

ones := 0
num := n
for _ in 0 ..= 7 {
if num & 1 == 1 {
ones += 1
}
num = num >> 1
}
return ones & 1 == 0
}

// Since these masks splits 8 bits into two parts: tr_chunk_masks[i] + tr_overflow_masks[i] = 0xFF
tr_chunk_masks := [?]u8{0xFE, 0xFC, 0xF8, 0xF0, 0xE0, 0xC0, 0x80}
tr_overflow_masks := [?]u8{0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F}

// Since these masks splits 7 bits into two parts: rc_chunk_masks[i] + rc_overflow_masks[i] = 0x7F
rc_chunk_masks := [?]u8{0x40, 0x60, 0x70, 0x78, 0x7C, 0x7E, 0x7F}
rc_overflow_masks := [?]u8{0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01, 0x00}
88 changes: 88 additions & 0 deletions exercises/practice/intergalactic-transmission/.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.

[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"
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package intergalactic_transmission

transmit_sequence :: proc(msg: []u8) -> (seq: []u8) {
// Implement this procedure.
return nil
}

decode_message :: proc(seq: []u8) -> (msg: []u8, okay: bool) {
// Implement this procedure.
return nil, false
}
Loading