Skip to content
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
17 changes: 17 additions & 0 deletions config.json
Original file line number Diff line number Diff line change
Expand Up @@ -2147,6 +2147,23 @@
],
"difficulty": 5
},
{
"slug": "intergalactic-transmission",
"name": "Intergalactic Transmission",
"uuid": "3ba8f12a-a873-4882-8eef-e44ebc3ff946",
"practices": [
"bit-manipulation",
"bitstrings"
],
"prerequisites": [
"atoms",
"bit-manipulation",
"bitstrings",
"pattern-matching",
"tuples"
],
"difficulty": 5
},
{
"slug": "luhn",
"name": "Luhn",
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 1s in each transmission and 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 right most 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.
4 changes: 4 additions & 0 deletions exercises/practice/intergalactic-transmission/.formatter.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]
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": [
"kahgoh"
],
"files": {
"solution": [
"lib/intergalactic_transmission.ex"
],
"test": [
"test/intergalactic_transmission_test.exs"
],
"example": [
".meta/example.ex"
]
},
"blurb": "Add parity bits to a message for transmission",
"source": "Kah Goh",
"source_url": "https://github.com/exercism/problem-specifications/pull/2543"
}
77 changes: 77 additions & 0 deletions exercises/practice/intergalactic-transmission/.meta/example.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
defmodule Transmission do
import Bitwise

@doc """
Return the transmission sequence for a message.
"""
@spec get_transmit_sequence(binary()) :: binary()
def get_transmit_sequence(message) do
encode(message)
end

@spec encode(bitstring(), bitstring()) :: binary()
defp encode(content, acc \\ <<>>)
defp encode(<<>>, acc), do: acc

defp encode(<<next::7, remaining::bitstring>>, acc) do
encode(remaining, <<acc::bitstring, add_parity(<<next::7>>)::bitstring>>)
end

defp encode(<<content::bitstring>>, acc) do
<<acc::bitstring, add_parity(<<content::bitstring>>)::bitstring>>
end

@spec add_parity(bitstring()) :: <<_::8>>
defp add_parity(data) when bit_size(data) < 8 do
<<data::bitstring, 0::size(7 - bit_size(data)), get_parity(data)::bitstring>>
end

@spec get_parity(bitstring()) :: <<_::1>>
defp get_parity(data) do
bit_count = bit_size(data)
<<value::size(bit_count)>> = data

parity_int =
0..(bit_count - 1)
|> Enum.map(fn
places ->
case value &&& 1 <<< places do
0 -> 0
_other -> 1
end
end)
|> Enum.reduce(0, fn elem, acc -> bxor(elem, acc) end)

case parity_int do
0 -> <<0::1>>
_ -> <<1::1>>
end
end

@doc """
Return the message decoded from the received transmission.
"""
@spec decode_message(binary()) :: {:ok, binary()} | {:error, String.t()}
def decode_message(<<>>), do: {:ok, <<>>}
def decode_message(received), do: do_decode(received)

@spec do_decode(bitstring(), bitstring()) :: {:ok, binary()} | {:error, String.t()}
defp do_decode(content, acc \\ <<>>)

defp do_decode(<<data::7, parity::1, remaining::bitstring>>, acc) do
if get_parity(<<data::7>>) == <<parity::1>> do
case remaining do
<<>> -> {:ok, finalize(<<data::7>>, acc)}
_ -> do_decode(remaining, <<acc::bitstring, data::7>>)
end
else
{:error, "wrong parity"}
end
end

defp finalize(data, acc) when bit_size(data) == 7 do
bits_count = 8 - rem(bit_size(acc), 8)
<<head::size(bits_count), _::bitstring>> = data
<<acc::bitstring, head::size(bits_count)>>
end
end
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,17 @@
defmodule Transmission do
Copy link
Member

Choose a reason for hiding this comment

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

The convention in elixir is that module names should match their filenames. You would need to commit to either to IntergalacticTransmission as the module name and rename this and the test module, or commit to Transmission and rename a bunch of files, as well as the module in the mix.exs file.

import Bitwise
Copy link
Member

Choose a reason for hiding this comment

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

It's possible to solve this exercise without bit operations (see below) so I would remove this import from the stub solution. If people use bit operations, then adding the right import should be part of the practice that they do 😁

  defp parity(bits) do
    list_of_bits =
      bits
      |> Stream.unfold(fn
        <<i::1, r::bitstring>> -> {i, r}
        <<>> -> nil
      end)
      |> Enum.into([])

    Integer.mod(Enum.sum(list_of_bits), 2)
  end

Copy link
Member Author

Choose a reason for hiding this comment

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

Totally agree - that shouldn't have been there 😅.


@doc """
Return the transmission sequence for a message.
"""
@spec get_transmit_sequence(binary()) :: binary()
def get_transmit_sequence(message) do
end

@doc """
Return the message decoded from the received transmission.
"""
@spec decode_message(binary()) :: {:ok, binary()} | {:error, String.t()}
def decode_message(received_data) do
end
end
28 changes: 28 additions & 0 deletions exercises/practice/intergalactic-transmission/mix.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
defmodule IntergalacticTransmission.MixProject do
use Mix.Project

def project do
[
app: :intergalactic_transmission,
version: "0.1.0",
# elixir: "~> 1.8",
start_permanent: Mix.env() == :prod,
deps: deps()
]
end

# Run "mix help compile.app" to learn about applications.
def application do
[
extra_applications: [:logger]
]
end

# Run "mix help deps" to learn about dependencies.
defp deps do
[
# {:dep_from_hexpm, "~> 0.3.0"},
# {:dep_from_git, git: "https://github.com/elixir-lang/my_dep.git", tag: "0.1.0"}
]
end
end
Loading
Loading