From b5a8d355136feb94d72a172e2d4cc23e4dca7db9 Mon Sep 17 00:00:00 2001 From: Danylo Negrienko Date: Thu, 27 Jun 2024 16:47:44 -0400 Subject: [PATCH] protein-translation --- .../protein-translation/.exercism/config.json | 26 ++++ .../.exercism/metadata.json | 1 + elixir/protein-translation/.formatter.exs | 4 + elixir/protein-translation/.gitignore | 24 +++ elixir/protein-translation/HELP.md | 75 ++++++++++ elixir/protein-translation/README.md | 69 +++++++++ .../lib/protein_translation.ex | 68 +++++++++ elixir/protein-translation/mix.exs | 28 ++++ .../test/protein_translation_test.exs | 139 ++++++++++++++++++ .../protein-translation/test/test_helper.exs | 2 + 10 files changed, 436 insertions(+) create mode 100644 elixir/protein-translation/.exercism/config.json create mode 100644 elixir/protein-translation/.exercism/metadata.json create mode 100644 elixir/protein-translation/.formatter.exs create mode 100644 elixir/protein-translation/.gitignore create mode 100644 elixir/protein-translation/HELP.md create mode 100644 elixir/protein-translation/README.md create mode 100644 elixir/protein-translation/lib/protein_translation.ex create mode 100644 elixir/protein-translation/mix.exs create mode 100644 elixir/protein-translation/test/protein_translation_test.exs create mode 100644 elixir/protein-translation/test/test_helper.exs diff --git a/elixir/protein-translation/.exercism/config.json b/elixir/protein-translation/.exercism/config.json new file mode 100644 index 0000000..40aa6b3 --- /dev/null +++ b/elixir/protein-translation/.exercism/config.json @@ -0,0 +1,26 @@ +{ + "authors": [ + "DoggettCK" + ], + "contributors": [ + "angelikatyborska", + "Cohen-Carlisle", + "devonestes", + "neenjaw", + "nwshane", + "sotojuan" + ], + "files": { + "solution": [ + "lib/protein_translation.ex" + ], + "test": [ + "test/protein_translation_test.exs" + ], + "example": [ + ".meta/example.ex" + ] + }, + "blurb": "Translate RNA sequences into proteins.", + "source": "Tyler Long" +} diff --git a/elixir/protein-translation/.exercism/metadata.json b/elixir/protein-translation/.exercism/metadata.json new file mode 100644 index 0000000..2425964 --- /dev/null +++ b/elixir/protein-translation/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"elixir","exercise":"protein-translation","id":"b19fcedc09cb4e6ab1ed560eaeb5cdec","url":"https://exercism.org/tracks/elixir/exercises/protein-translation","handle":"negrienko","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/elixir/protein-translation/.formatter.exs b/elixir/protein-translation/.formatter.exs new file mode 100644 index 0000000..d2cda26 --- /dev/null +++ b/elixir/protein-translation/.formatter.exs @@ -0,0 +1,4 @@ +# Used by "mix format" +[ + inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"] +] diff --git a/elixir/protein-translation/.gitignore b/elixir/protein-translation/.gitignore new file mode 100644 index 0000000..6104b84 --- /dev/null +++ b/elixir/protein-translation/.gitignore @@ -0,0 +1,24 @@ +# The directory Mix will write compiled artifacts to. +/_build/ + +# If you run "mix test --cover", coverage assets end up here. +/cover/ + +# The directory Mix downloads your dependencies sources to. +/deps/ + +# Where third-party dependencies like ExDoc output generated docs. +/doc/ + +# Ignore .fetch files in case you like to edit your project deps locally. +/.fetch + +# If the VM crashes, it generates a dump, let's ignore it too. +erl_crash.dump + +# Also ignore archive artifacts (built via "mix archive.build"). +*.ez + +# Ignore package tarball (built via "mix hex.build"). +protein_translation-*.tar + diff --git a/elixir/protein-translation/HELP.md b/elixir/protein-translation/HELP.md new file mode 100644 index 0000000..d39d7be --- /dev/null +++ b/elixir/protein-translation/HELP.md @@ -0,0 +1,75 @@ +# Help + +## Running the tests + +From the terminal, change to the base directory of the exercise then execute the tests with: + +```bash +$ mix test +``` + +This will execute the test file found in the `test` subfolder -- a file ending in `_test.exs` + +Documentation: + +* [`mix test` - Elixir's test execution tool](https://hexdocs.pm/mix/Mix.Tasks.Test.html) +* [`ExUnit` - Elixir's unit test library](https://hexdocs.pm/ex_unit/ExUnit.html) + +## Pending tests + +In test suites of practice exercises, all but the first test have been tagged to be skipped. + +Once you get a test passing, you can unskip the next one by commenting out the relevant `@tag :pending` with a `#` symbol. + +For example: + +```elixir +# @tag :pending +test "shouting" do + assert Bob.hey("WATCH OUT!") == "Whoa, chill out!" +end +``` + +If you wish to run all tests at once, you can include all skipped test by using the `--include` flag on the `mix test` command: + +```bash +$ mix test --include pending +``` + +Or, you can enable all the tests by commenting out the `ExUnit.configure` line in the file `test/test_helper.exs`. + +```elixir +# ExUnit.configure(exclude: :pending, trace: true) +``` + +## Useful `mix test` options + +* `test/.exs:LINENUM` - runs only a single test, the test from `.exs` whose definition is on line `LINENUM` +* `--failed` - runs only tests that failed the last time they ran +* `--max-failures` - the suite stops evaluating tests when this number of test failures +is reached +* `--seed 0` - disables randomization so the tests in a single file will always be ran +in the same order they were defined in + +## Submitting your solution + +You can submit your solution using the `exercism submit lib/protein_translation.ex` command. +This command will upload your solution to the Exercism website and print the solution page's URL. + +It's possible to submit an incomplete solution which allows you to: + +- See how others have completed the exercise +- Request help from a mentor + +## Need to get help? + +If you'd like help solving the exercise, check the following pages: + +- The [Elixir track's documentation](https://exercism.org/docs/tracks/elixir) +- The [Elixir track's programming category on the forum](https://forum.exercism.org/c/programming/elixir) +- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5) +- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) + +Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. + +If you're stuck on something, it may help to look at some of the [available resources](https://exercism.org/docs/tracks/elixir/resources) out there where answers might be found. \ No newline at end of file diff --git a/elixir/protein-translation/README.md b/elixir/protein-translation/README.md new file mode 100644 index 0000000..f5a1a3e --- /dev/null +++ b/elixir/protein-translation/README.md @@ -0,0 +1,69 @@ +# Protein Translation + +Welcome to Protein Translation on Exercism's Elixir Track. +If you need help running the tests or submitting your code, check out `HELP.md`. + +## Instructions + +Translate RNA sequences into proteins. + +RNA can be broken into three nucleotide sequences called codons, and then translated to a polypeptide like so: + +RNA: `"AUGUUUUCU"` => translates to + +Codons: `"AUG", "UUU", "UCU"` +=> which become a polypeptide with the following sequence => + +Protein: `"Methionine", "Phenylalanine", "Serine"` + +There are 64 codons which in turn correspond to 20 amino acids; however, all of the codon sequences and resulting amino acids are not important in this exercise. +If it works for one codon, the program should work for all of them. +However, feel free to expand the list in the test suite to include them all. + +There are also three terminating codons (also known as 'STOP' codons); if any of these codons are encountered (by the ribosome), all translation ends and the protein is terminated. + +All subsequent codons after are ignored, like this: + +RNA: `"AUGUUUUCUUAAAUG"` => + +Codons: `"AUG", "UUU", "UCU", "UAA", "AUG"` => + +Protein: `"Methionine", "Phenylalanine", "Serine"` + +Note the stop codon `"UAA"` terminates the translation and the final methionine is not translated into the protein sequence. + +Below are the codons and resulting Amino Acids needed for the exercise. + +| Codon | Protein | +| :----------------- | :------------ | +| AUG | Methionine | +| UUU, UUC | Phenylalanine | +| UUA, UUG | Leucine | +| UCU, UCC, UCA, UCG | Serine | +| UAU, UAC | Tyrosine | +| UGU, UGC | Cysteine | +| UGG | Tryptophan | +| UAA, UAG, UGA | STOP | + +Learn more about [protein translation on Wikipedia][protein-translation]. + +[protein-translation]: https://en.wikipedia.org/wiki/Translation_(biology) + +## Source + +### Created by + +- @DoggettCK + +### Contributed to by + +- @angelikatyborska +- @Cohen-Carlisle +- @devonestes +- @neenjaw +- @nwshane +- @sotojuan + +### Based on + +Tyler Long \ No newline at end of file diff --git a/elixir/protein-translation/lib/protein_translation.ex b/elixir/protein-translation/lib/protein_translation.ex new file mode 100644 index 0000000..340c030 --- /dev/null +++ b/elixir/protein-translation/lib/protein_translation.ex @@ -0,0 +1,68 @@ +defmodule ProteinTranslation do + @doc """ + Given an RNA string, return a list of proteins specified by codons, in order. + """ + @spec of_rna(String.t()) :: {:ok, list(String.t())} | {:error, String.t()} + def of_rna(""), do: {:ok, []} + + def of_rna(rna) do + rna + |> String.graphemes() + |> Enum.chunk_every(3) + |> Enum.map(&Enum.join/1) + |> Enum.reduce_while([], &reducer/2) + |> case do + proteines when is_list(proteines) -> {:ok, Enum.reverse(proteines)} + {:error, error} -> {:error, error} + end + end + + defp reducer(codon, proteines) do + case of_codon(codon) do + {:error, _error} -> {:halt, {:error, "invalid RNA"}} + {:ok, "STOP"} -> {:halt, proteines} + {:ok, proteine} -> {:cont, [proteine | proteines]} + end + end + + @doc """ + Given a codon, return the corresponding protein + + UGU -> Cysteine + UGC -> Cysteine + UUA -> Leucine + UUG -> Leucine + AUG -> Methionine + UUU -> Phenylalanine + UUC -> Phenylalanine + UCU -> Serine + UCC -> Serine + UCA -> Serine + UCG -> Serine + UGG -> Tryptophan + UAU -> Tyrosine + UAC -> Tyrosine + UAA -> STOP + UAG -> STOP + UGA -> STOP + """ + @cysteines ~w(UGU UGC) + @leucines ~w(UUA UUG) + @tyrosines ~w(UAU UAC) + @phenylalanines ~w(UUU UUC) + @serines ~w(UCU UCC UCA UCG) + @methionines ~w(AUG) + @tryptophans ~w(UGG) + @stops ~w(UAA UAG UGA) + + @spec of_codon(String.t()) :: {:ok, String.t()} | {:error, String.t()} + def of_codon(cysteine) when cysteine in @cysteines, do: {:ok, "Cysteine"} + def of_codon(leucine) when leucine in @leucines, do: {:ok, "Leucine"} + def of_codon(tyrosine) when tyrosine in @tyrosines, do: {:ok, "Tyrosine"} + def of_codon(phenylalanine) when phenylalanine in @phenylalanines, do: {:ok, "Phenylalanine"} + def of_codon(serine) when serine in @serines, do: {:ok, "Serine"} + def of_codon(methionine) when methionine in @methionines, do: {:ok, "Methionine"} + def of_codon(tryptophan) when tryptophan in @tryptophans, do: {:ok, "Tryptophan"} + def of_codon(stop) when stop in @stops, do: {:ok, "STOP"} + def of_codon(_invalid), do: {:error, "invalid codon"} +end diff --git a/elixir/protein-translation/mix.exs b/elixir/protein-translation/mix.exs new file mode 100644 index 0000000..9aea540 --- /dev/null +++ b/elixir/protein-translation/mix.exs @@ -0,0 +1,28 @@ +defmodule ProteinTranslation.MixProject do + use Mix.Project + + def project do + [ + app: :protein_translation, + 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/elixir/protein-translation/test/protein_translation_test.exs b/elixir/protein-translation/test/protein_translation_test.exs new file mode 100644 index 0000000..2d23a32 --- /dev/null +++ b/elixir/protein-translation/test/protein_translation_test.exs @@ -0,0 +1,139 @@ +defmodule ProteinTranslationTest do + use ExUnit.Case + + describe "of_codon" do + test "AUG translates to methionine" do + assert ProteinTranslation.of_codon("AUG") == {:ok, "Methionine"} + end + + test "identifies Phenylalanine codons" do + assert ProteinTranslation.of_codon("UUU") == {:ok, "Phenylalanine"} + assert ProteinTranslation.of_codon("UUC") == {:ok, "Phenylalanine"} + end + + test "identifies Leucine codons" do + assert ProteinTranslation.of_codon("UUA") == {:ok, "Leucine"} + assert ProteinTranslation.of_codon("UUG") == {:ok, "Leucine"} + end + + test "identifies Serine codons" do + assert ProteinTranslation.of_codon("UCU") == {:ok, "Serine"} + assert ProteinTranslation.of_codon("UCC") == {:ok, "Serine"} + assert ProteinTranslation.of_codon("UCA") == {:ok, "Serine"} + assert ProteinTranslation.of_codon("UCG") == {:ok, "Serine"} + end + + test "identifies Tyrosine codons" do + assert ProteinTranslation.of_codon("UAU") == {:ok, "Tyrosine"} + assert ProteinTranslation.of_codon("UAC") == {:ok, "Tyrosine"} + end + + test "identifies Cysteine codons" do + assert ProteinTranslation.of_codon("UGU") == {:ok, "Cysteine"} + assert ProteinTranslation.of_codon("UGC") == {:ok, "Cysteine"} + end + + test "identifies Tryptophan codons" do + assert ProteinTranslation.of_codon("UGG") == {:ok, "Tryptophan"} + end + + test "identifies stop codons" do + assert ProteinTranslation.of_codon("UAA") == {:ok, "STOP"} + assert ProteinTranslation.of_codon("UAG") == {:ok, "STOP"} + assert ProteinTranslation.of_codon("UGA") == {:ok, "STOP"} + end + + test "incomplete codon" do + assert ProteinTranslation.of_codon("UG") == {:error, "invalid codon"} + end + + test "too long, invalid codon" do + assert ProteinTranslation.of_codon("UGGG") == {:error, "invalid codon"} + end + + test "known amino acids, but invalid codon" do + assert ProteinTranslation.of_codon("AAA") == {:error, "invalid codon"} + end + + test "unknown amino acids, not part of a codon" do + assert ProteinTranslation.of_codon("XYZ") == {:error, "invalid codon"} + end + end + + describe "of_rna" do + test "empty RNA sequence results in no proteins" do + strand = "" + assert ProteinTranslation.of_rna(strand) == {:ok, []} + end + + test "translates rna strand into correct protein" do + strand = "AUGUUUUGG" + assert ProteinTranslation.of_rna(strand) == {:ok, ~w(Methionine Phenylalanine Tryptophan)} + end + + test "sequence of two identical protein codons translates into two identical proteins" do + strand = "UUUUUU" + assert ProteinTranslation.of_rna(strand) == {:ok, ~w(Phenylalanine Phenylalanine)} + end + + test "sequence of two different protein codons translates into two identical proteins" do + strand = "UUAUUG" + assert ProteinTranslation.of_rna(strand) == {:ok, ~w(Leucine Leucine)} + end + + test "stops translation if stop codon preset at beginning of sequence" do + strand = "UAGUGG" + assert ProteinTranslation.of_rna(strand) == {:ok, ~w()} + end + + test "stops translation if stop codon present at end of two-codon sequence" do + strand = "UGGUAG" + assert ProteinTranslation.of_rna(strand) == {:ok, ~w(Tryptophan)} + end + + test "stops translation if stop codon present at end of three-codon sequence" do + strand = "AUGUUUUAA" + assert ProteinTranslation.of_rna(strand) == {:ok, ~w(Methionine Phenylalanine)} + end + + test "stops translation if stop codon present in middle of three-codon sequence" do + strand = "UGGUAGUGG" + assert ProteinTranslation.of_rna(strand) == {:ok, ~w(Tryptophan)} + end + + test "stops translation if stop codon present in middle of six-codon sequence" do + strand = "UGGUGUUAUUAAUGGUUU" + assert ProteinTranslation.of_rna(strand) == {:ok, ~w(Tryptophan Cysteine Tyrosine)} + end + + test "incomplete codon, invalid RNA" do + strand = "UG" + assert ProteinTranslation.of_rna(strand) == {:error, "invalid RNA"} + end + + test "known amino acids, but invalid codon, invalid RNA" do + strand = "AAA" + assert ProteinTranslation.of_rna(strand) == {:error, "invalid RNA"} + end + + test "unknown amino acids, not part of a codon, invalid RNA" do + strand = "XYZ" + assert ProteinTranslation.of_rna(strand) == {:error, "invalid RNA"} + end + + test "invalid codon at end of RNA" do + strand = "UUUROT" + assert ProteinTranslation.of_rna(strand) == {:error, "invalid RNA"} + end + + test "incomplete RNA" do + strand = "AUGU" + assert ProteinTranslation.of_rna(strand) == {:error, "invalid RNA"} + end + + test "incomplete RNA valid until a STOP codon" do + strand = "UUCUUCUAAUGGU" + assert ProteinTranslation.of_rna(strand) == {:ok, ~w(Phenylalanine Phenylalanine)} + end + end +end diff --git a/elixir/protein-translation/test/test_helper.exs b/elixir/protein-translation/test/test_helper.exs new file mode 100644 index 0000000..35fc5bf --- /dev/null +++ b/elixir/protein-translation/test/test_helper.exs @@ -0,0 +1,2 @@ +ExUnit.start() +ExUnit.configure(exclude: :pending, trace: true)