diff --git a/config.json b/config.json index 712dbd6ee..e3f9f3700 100644 --- a/config.json +++ b/config.json @@ -2147,6 +2147,28 @@ ], "difficulty": 5 }, + { + "slug": "intergalactic-transmission", + "name": "Intergalactic Transmission", + "uuid": "3ba8f12a-a873-4882-8eef-e44ebc3ff946", + "practices": [ + "bit-manipulation", + "bitstrings" + ], + "prerequisites": [ + "atoms", + "bit-manipulation", + "bitstrings", + "case", + "enum", + "if", + "pattern-matching", + "recursion", + "tail-call-recursion", + "tuples" + ], + "difficulty": 5 + }, { "slug": "luhn", "name": "Luhn", diff --git a/exercises/practice/intergalactic-transmission/.docs/instructions.md b/exercises/practice/intergalactic-transmission/.docs/instructions.md new file mode 100644 index 000000000..63fe661e0 --- /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 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). diff --git a/exercises/practice/intergalactic-transmission/.docs/introduction.md b/exercises/practice/intergalactic-transmission/.docs/introduction.md new file mode 100644 index 000000000..f19dffbea --- /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/.formatter.exs b/exercises/practice/intergalactic-transmission/.formatter.exs new file mode 100644 index 000000000..d2cda26ed --- /dev/null +++ b/exercises/practice/intergalactic-transmission/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/exercises/practice/intergalactic-transmission/.meta/config.json b/exercises/practice/intergalactic-transmission/.meta/config.json new file mode 100644 index 000000000..df9dd8103 --- /dev/null +++ b/exercises/practice/intergalactic-transmission/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "kahgoh" + ], + "files": { + "solution": [ + "lib/transmission.ex" + ], + "test": [ + "test/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" +} diff --git a/exercises/practice/intergalactic-transmission/.meta/example.ex b/exercises/practice/intergalactic-transmission/.meta/example.ex new file mode 100644 index 000000000..b905f6ad1 --- /dev/null +++ b/exercises/practice/intergalactic-transmission/.meta/example.ex @@ -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(<>, acc) do + encode(remaining, <>)::bitstring>>) + end + + defp encode(<>, acc) do + <>)::bitstring>> + end + + @spec add_parity(bitstring()) :: <<_::8>> + defp add_parity(data) when bit_size(data) < 8 do + <> + end + + @spec get_parity(bitstring()) :: <<_::1>> + defp get_parity(data) do + bit_count = bit_size(data) + <> = 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(<>, acc) do + if get_parity(<>) == <> do + case remaining do + <<>> -> {:ok, finalize(<>, acc)} + _ -> do_decode(remaining, <>) + 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) + <> = data + <> + 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 000000000..64a8aaced --- /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/lib/transmission.ex b/exercises/practice/intergalactic-transmission/lib/transmission.ex new file mode 100644 index 000000000..392b87652 --- /dev/null +++ b/exercises/practice/intergalactic-transmission/lib/transmission.ex @@ -0,0 +1,15 @@ +defmodule Transmission do + @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 diff --git a/exercises/practice/intergalactic-transmission/mix.exs b/exercises/practice/intergalactic-transmission/mix.exs new file mode 100644 index 000000000..03f53e84f --- /dev/null +++ b/exercises/practice/intergalactic-transmission/mix.exs @@ -0,0 +1,28 @@ +defmodule Transmission.MixProject do + use Mix.Project + + def project do + [ + app: :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 diff --git a/exercises/practice/intergalactic-transmission/test/test_helper.exs b/exercises/practice/intergalactic-transmission/test/test_helper.exs new file mode 100644 index 000000000..35fc5bff8 --- /dev/null +++ b/exercises/practice/intergalactic-transmission/test/test_helper.exs @@ -0,0 +1,2 @@ +ExUnit.start() +ExUnit.configure(exclude: :pending, trace: true) diff --git a/exercises/practice/intergalactic-transmission/test/transmission_test.exs b/exercises/practice/intergalactic-transmission/test/transmission_test.exs new file mode 100644 index 000000000..fff2e9ecd --- /dev/null +++ b/exercises/practice/intergalactic-transmission/test/transmission_test.exs @@ -0,0 +1,162 @@ +defmodule TransmissionTest do + use ExUnit.Case + + describe "calculate transmit sequences" do + test "empty message" do + assert Transmission.get_transmit_sequence(<<>>) == <<>> + end + + @tag :pending + test "0x00 is transmitted as 0x0000" do + assert Transmission.get_transmit_sequence(<<0x00>>) == <<0x00, 0x00>> + end + + @tag :pending + test "0x02 is transmitted as 0x0300" do + assert Transmission.get_transmit_sequence(<<0x02>>) == <<0x03, 0x00>> + end + + @tag :pending + test "0x06 is transmitted as 0x0600" do + assert Transmission.get_transmit_sequence(<<0x06>>) == <<0x06, 0x00>> + end + + @tag :pending + test "0x05 is transmitted as 0x0581" do + assert Transmission.get_transmit_sequence(<<0x05>>) == <<0x05, 0x81>> + end + + @tag :pending + test "0x29 is transmitted as 0x2881" do + assert Transmission.get_transmit_sequence(<<0x29>>) == <<0x28, 0x81>> + end + + @tag :pending + test "0xc001c0de is transmitted as 0xc000711be1" do + assert Transmission.get_transmit_sequence(<<0xC0, 0x01, 0xC0, 0xDE>>) == + <<0xC0, 0x00, 0x71, 0x1B, 0xE1>> + end + + @tag :pending + test "six byte message" do + assert Transmission.get_transmit_sequence(<<0x47, 0x72, 0x65, 0x61, 0x74, 0x21>>) == + <<0x47, 0xB8, 0x99, 0xAC, 0x17, 0xA0, 0x84>> + end + + @tag :pending + test "seven byte message" do + assert Transmission.get_transmit_sequence(<<0x47, 0x72, 0x65, 0x61, 0x74, 0x31, 0x21>>) == + <<0x47, 0xB8, 0x99, 0xAC, 0x17, 0xA0, 0xC5, 0x42>> + end + + @tag :pending + test "eight byte message" do + assert Transmission.get_transmit_sequence( + <<0xC0, 0x01, 0x13, 0x37, 0xC0, 0xDE, 0x21, 0x21>> + ) == + <<0xC0, 0x00, 0x44, 0x66, 0x7D, 0x06, 0x78, 0x42, 0x21, 0x81>> + end + + @tag :pending + test "twenty byte message" do + assert Transmission.get_transmit_sequence( + <<0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x6D, 0x20, 0x69, 0x73, 0x20, 0x61, + 0x77, 0x65, 0x73, 0x6F, 0x6D, 0x65, 0x21>> + ) == + <<0x44, 0xBD, 0x18, 0xAF, 0x27, 0x1B, 0xA5, 0xE7, 0x6C, 0x90, 0x1B, 0x2E, 0x33, + 0x03, 0x84, 0xEE, 0x65, 0xB8, 0xDB, 0xED, 0xD7, 0x28, 0x84>> + end + end + + describe "decode received messages" do + @tag :pending + test "empty message" do + assert Transmission.decode_message(<<>>) == {:ok, <<>>} + end + + @tag :pending + test "zero message" do + assert Transmission.decode_message(<<0x00, 0x00>>) == {:ok, <<0x00>>} + end + + @tag :pending + test "0x0300 is decoded to 0x02" do + assert Transmission.decode_message(<<0x03, 0x00>>) == {:ok, <<0x02>>} + end + + @tag :pending + test "0x0581 is decoded to 0x05" do + assert Transmission.decode_message(<<0x05, 0x81>>) == {:ok, <<0x05>>} + end + + @tag :pending + test "0x2881 is decoded to 0x29" do + assert Transmission.decode_message(<<0x28, 0x81>>) == {:ok, <<0x29>>} + end + + @tag :pending + test "first byte has wrong parity" do + assert Transmission.decode_message(<<0x07, 0x00>>) == {:error, "wrong parity"} + end + + @tag :pending + test "second byte has wrong parity" do + assert Transmission.decode_message(<<0x03, 0x68>>) == {:error, "wrong parity"} + end + + @tag :pending + test "0xcf4b00 is decoded to 0xce94" do + assert Transmission.decode_message(<<0xCF, 0x4B, 0x00>>) == {:ok, <<0xCE, 0x94>>} + end + + @tag :pending + test "0xe2566500 is decoded to 0xe2ad90" do + assert Transmission.decode_message(<<0xE2, 0x56, 0x65, 0x00>>) == + {:ok, <<0xE2, 0xAD, 0x90>>} + end + + @tag :pending + test "six byte message" do + assert Transmission.decode_message(<<0x47, 0xB8, 0x99, 0xAC, 0x17, 0xA0, 0x84>>) == + {:ok, <<0x47, 0x72, 0x65, 0x61, 0x74, 0x21>>} + end + + @tag :pending + test "seven byte message" do + assert Transmission.decode_message(<<0x47, 0xB8, 0x99, 0xAC, 0x17, 0xA0, 0xC5, 0x42>>) == + {:ok, <<0x47, 0x72, 0x65, 0x61, 0x74, 0x31, 0x21>>} + end + + @tag :pending + test "last byte has wrong parity" do + assert Transmission.decode_message(<<0x47, 0xB8, 0x99, 0xAC, 0x17, 0xA0, 0xC5, 0x43>>) == + {:error, "wrong parity"} + end + + @tag :pending + test "eigth byte message" do + assert Transmission.decode_message( + <<0xC0, 0x00, 0x44, 0x66, 0x7D, 0x06, 0x78, 0x42, 0x21, 0x81>> + ) == {:ok, <<0xC0, 0x01, 0x13, 0x37, 0xC0, 0xDE, 0x21, 0x21>>} + end + + @tag :pending + test "twenty byte message" do + assert Transmission.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>> + ) == + {:ok, + <<0x45, 0x78, 0x65, 0x72, 0x63, 0x69, 0x73, 0x6D, 0x20, 0x69, 0x73, 0x20, 0x61, + 0x77, 0x65, 0x73, 0x6F, 0x6D, 0x65, 0x21>>} + end + + @tag :pending + test "wrong parity on 16th byte" do + assert Transmission.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>> + ) == {:error, "wrong parity"} + end + end +end