1
\$\begingroup\$

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.

Learn more about protein translation on Wikipedia

This was the task given. I originally did this some time ago in Python 3.

def proteins(strand):
    sub_len = 3
    split_str = [strand[i:i+sub_len] for i in range(0, len(strand), sub_len)]
    protein = []

    for x in split_str:
        if x == "UAA" or x == "UAG" or x == "UGA":
            break

        elif x == "AUG":
            protein.append("Methionine")

        elif x == "UUU" or x == "UUC":
            protein.append("Phenylalanine")

        elif x == "UUA" or x == "UUG":
            protein.append("Leucine")

        elif x == "UCU" or x == "UCC" or x == "UCA" or x == "UCG":
            protein.append("Serine")

        elif x == "UAU" or x == "UAC":
            protein.append("Tyrosine")

        elif x == "UGU" or x == "UGC":
            protein.append("Cysteine")

        elif x == "UGG":
            protein.append("Tryptophan")

    return protein

This time I did this in C#.

// This file was auto-generated based on version 1.1.1 of the canonical data.

using System;
using System.Collections.Generic;
using System.Linq;

public static class ProteinTranslation
{
    public static string[] Proteins(string strand)
    {
        //  Create a list to house codons
        List<string> protein = new List<string>();
        //  Convert string(RNA aka strand) to Array so we can iterate in chunks of 3's(codons)
        IEnumerable<string> output = RnaToCodons(strand);
        //  Add codons to list and return results
        return Codons(protein, output);
    }

    private static IEnumerable<string> RnaToCodons(string strand, int k = 0) => strand.ToLookup(c => Math.Floor(k++ / (double)3)).Select(e => new String(e.ToArray()));

    private static string[] Codons(List<string> protein, IEnumerable<string> output)
    {
        foreach (var item in output)
        {
            switch (item)
            {
                case "UAA": case "UAG": case "UGA": return protein.ToArray();
                case "UCU": case "UCC": case "UCA": case "UCG": protein.Add("Serine"); break;
                case "UUU": case "UUC": protein.Add("Phenylalanine"); break;
                case "UUA": case "UUG": protein.Add("Leucine"); break;
                case "UAU": case "UAC": protein.Add("Tyrosine"); break;
                case "UGU": case "UGC": protein.Add("Cysteine"); break;
                case "UGG": protein.Add("Tryptophan"); break;
                case "AUG": protein.Add("Methionine"); break;
            }
        }
        return protein.ToArray();
    }
}

Requiring me to pass these test.

// This file was auto-generated based on version 1.1.1 of the canonical data.

using Xunit;

public class ProteinTranslationTests
{
    [Fact]
    public void Methionine_rna_sequence() => Assert.Equal(new[] { "Methionine" }, ProteinTranslation.Proteins("AUG"));

    [Fact]
    public void Phenylalanine_rna_sequence_1() => Assert.Equal(new[] { "Phenylalanine" }, ProteinTranslation.Proteins("UUU"));

    [Fact]
    public void Phenylalanine_rna_sequence_2() => Assert.Equal(new[] { "Phenylalanine" }, ProteinTranslation.Proteins("UUC"));

    [Fact]
    public void Leucine_rna_sequence_1() => Assert.Equal(new[] { "Leucine" }, ProteinTranslation.Proteins("UUA"));

    [Fact]
    public void Leucine_rna_sequence_2() => Assert.Equal(new[] { "Leucine" }, ProteinTranslation.Proteins("UUG"));

    [Fact]
    public void Serine_rna_sequence_1() => Assert.Equal(new[] { "Serine" }, ProteinTranslation.Proteins("UCU"));

    [Fact]
    public void Serine_rna_sequence_2() => Assert.Equal(new[] { "Serine" }, ProteinTranslation.Proteins("UCC"));

    [Fact]
    public void Serine_rna_sequence_3() => Assert.Equal(new[] { "Serine" }, ProteinTranslation.Proteins("UCA"));

    [Fact]
    public void Serine_rna_sequence_4() => Assert.Equal(new[] { "Serine" }, ProteinTranslation.Proteins("UCG"));

    [Fact]
    public void Tyrosine_rna_sequence_1() => Assert.Equal(new[] { "Tyrosine" }, ProteinTranslation.Proteins("UAU"));

    [Fact]
    public void Tyrosine_rna_sequence_2() => Assert.Equal(new[] { "Tyrosine" }, ProteinTranslation.Proteins("UAC"));

    [Fact]
    public void Cysteine_rna_sequence_1() => Assert.Equal(new[] { "Cysteine" }, ProteinTranslation.Proteins("UGU"));

    [Fact]
    public void Cysteine_rna_sequence_2() => Assert.Equal(new[] { "Cysteine" }, ProteinTranslation.Proteins("UGC"));

    [Fact]
    public void Tryptophan_rna_sequence() => Assert.Equal(new[] { "Tryptophan" }, ProteinTranslation.Proteins("UGG"));

    [Fact]
    public void Stop_codon_rna_sequence_1() => Assert.Empty(ProteinTranslation.Proteins("UAA"));

    [Fact]
    public void Stop_codon_rna_sequence_2() => Assert.Empty(ProteinTranslation.Proteins("UAG"));

    [Fact]
    public void Stop_codon_rna_sequence_3() => Assert.Empty(ProteinTranslation.Proteins("UGA"));

    [Fact]
    public void Translate_rna_strand_into_correct_protein_list() => Assert.Equal(new[] { "Methionine", "Phenylalanine", "Tryptophan" }, ProteinTranslation.Proteins("AUGUUUUGG"));

    [Fact]
    public void Translation_stops_if_stop_codon_at_beginning_of_sequence() => Assert.Empty(ProteinTranslation.Proteins("UAGUGG"));

    [Fact]
    public void Translation_stops_if_stop_codon_at_end_of_two_codon_sequence() => Assert.Equal(new[] { "Tryptophan" }, ProteinTranslation.Proteins("UGGUAG"));

    [Fact]
    public void Translation_stops_if_stop_codon_at_end_of_three_codon_sequence() => Assert.Equal(new[] { "Methionine", "Phenylalanine" }, ProteinTranslation.Proteins("AUGUUUUAA"));

    [Fact]
    public void Translation_stops_if_stop_codon_in_middle_of_three_codon_sequence() => Assert.Equal(new[] { "Tryptophan" }, ProteinTranslation.Proteins("UGGUAGUGG"));

    [Fact]
    public void Translation_stops_if_stop_codon_in_middle_of_six_codon_sequence() => Assert.Equal(new[] { "Tryptophan", "Cysteine", "Tyrosine" }, ProteinTranslation.Proteins("UGGUGUUAUUAAUGGUUU"));
}

I had issues on deciding how to iterate a string with Nth length of a substring. split_str and RnaToCodons was code I borrowed from post on StackOverflow. I'm not sure, but I have a feeling that a better way to do this exist. In C#, I wanted to decouple my code unlike the Python version I made. I wanted to make sure I only made one pass through the given string with Codons. Not sure if the switch case was the best way to go here, but in my opinion its easy to read.

It will be interesting to see if this can be sped up or more concise while still be readable along with learning if there is a better way to iterate a string with a substring.

\$\endgroup\$
  • \$\begingroup\$ What should the behavior be, if an invalid RNA (e.g. 'UXU') sequence is found - is that a mutation or should it terminate the search? \$\endgroup\$ – user73941 May 20 '20 at 9:12
1
\$\begingroup\$

Your implementation doesn't seem to bother if the RNA-sequence contains invalid characters like: "UXGUGUUAUUA". Is that on purpose? I think, I would expect an exception or at least some reporting in a log.


An alternative to a switch-statement is often a dictionary - especially if the cases are going to vary or maybe should be localized - because a dictionary can be loaded at runtime from a file or database:

static readonly IDictionary<string, string> rnaProteinMap = new Dictionary<string, string>
{
  { "UAA", null },
  { "UAG", null },
  { "UGA", null },

  { "UCU", "Serine" },
  { "UCC", "Serine" },
  { "UCA", "Serine" },
  { "UCG", "Serine" },

  { "UUU", "Phenylalanine" },
  { "UUC", "Phenylalanine" },

  { "UUA", "Leucine" },
  { "UUG", "Leucine" },

  { "UAU", "Tyrosine" },
  { "UAC", "Tyrosine" },

  { "UGU", "Cysteine" },
  { "UGC", "Cysteine" },

  { "UGG", "Tryptophan" },

  { "AUG", "Methionine" },
};

Here more RNA-entries map to the same protein, but I don't think that's an issue in this context.


private static string[] Codons(List<string> protein, IEnumerable<string> output) I don't understand, why you have protein as an argument instead of just create it in Codons()?


Below, I have refactored your code using the same bits an pieces in another fasion:

  private static IEnumerable<string> RnaToCodons(string strand, int k = 0) => strand.ToLookup(c => Math.Floor(k++ / (double)3)).Select(e => new String(e.ToArray()));

  private static bool TryGetProtein(string rna, out string protein)
  {
    protein = null;

    switch (rna)
    {
      case "UAA": case "UAG": case "UGA": 
        return false;
      case "UCU": case "UCC": case "UCA": case "UCG":
        protein = "Serine";
        break;
      case "UUU": case "UUC":
        protein = "Phenylalanine";
        break;
      case "UUA": case "UUG":
        protein = "Leucine";
        break;
      case "UAU": case "UAC":
        protein = "Tyrosine";
        break;
      case "UGU": case "UGC":
        protein = "Cysteine";
        break;
      case "UGG":
        protein = "Tryptophan";
        break;
      case "AUG":
        protein = "Methionine";
        break;
      default:
        // TODO log an invalid RNA
        return true;
        // OR throw new ArgumentException($"Invalid RNA sequence: {rna}", nameof(rna));
    }

    return true;
  }

  public static string[] Proteins(string strand)
  {
    List<string> proteins = new List<string>();

    foreach (var rna in RnaToCodons(strand))
    {
      if (!TryGetProtein(rna, out string protein))
        break;
      if (protein != null)
        proteins.Add(protein);
    }

    return proteins.ToArray();
  }

In TryGetProtein I return true for an invalid RNA-sequence after reporting it to the log in order to let the process proceed instead of terminate it with an exception. You should consider what to do in such situations.

RnaToCodons() seems to be the bottleneck performance wise. You should try stress tests it with a huge RNA-string.


Below is another solution that handles everything in one iteration:

IEnumerable<string> Slice(string data, int size)
{
  if (size <= 0) throw new ArgumentOutOfRangeException(nameof(size), "Must be greater than zero");


  char[] slice = new char[size];

  for (int i = 0; i <= data.Length; i++)
  {
    if (i > 0 && i % size == 0)
    {
      yield return new string(slice);
    }

    if (i == data.Length)
      yield break;

    slice[i % size] = data[i];
  }

}

IEnumerable<string> Proteins(string strand)
{
  foreach (string rna in Slice(strand, 3))
  {
    if (rnaProteinMap.TryGetValue(rna, out string protein))
    {
      if (protein == null) yield break;
      yield return protein;
    }
    else
    {
      // throw, report an error or just let is pass, as you do?
    }  
  }
}

It uses the dictionary rnaProteinMap as shown above.

\$\endgroup\$
  • \$\begingroup\$ Your implementation doesn't seem to bother if the RNA-sequence contains invalid characters like: "UXGUGUUAUUA". Is that on purpose? I think, I would expect an exception or at least some reporting in a log. Correct, I did not factor that in or think about that. private static string[] Codons(List<string> protein, IEnumerable<string> output) I don't understand, why you have protein as an argument instead of just create it in Codons()? I let VSC do a refactor that turned it into that. I cannot recall what it was prior(I tried looking at the commit history on github). \$\endgroup\$ – Milliorn May 20 '20 at 19:12
  • \$\begingroup\$ That is a very clever and unique way from what I have seen to slice up a string. I'm still working it out in my head how it works, but it makes the foreach easier to read. I had the idea to go with a dictionary originally but gave up after some trouble early on. I'm just a lot more familiar with switch/case. \$\endgroup\$ – Milliorn May 20 '20 at 19:33
  • \$\begingroup\$ Trying to understand Slice. if (i > 0 && i % size == 0) { yield return new string(slice); } Is this simply returning an empty string/character if the size is 0? Guess I am just having trouble understanding how this function works step by step an how it feds the foreach a substring? \$\endgroup\$ – Milliorn May 20 '20 at 19:34
  • \$\begingroup\$ @Milliorn: If size is zero, the a DivideByZeroException is thrown, so you should probably check size before using it. It should be greater than zero to give any meaning - see my update. \$\endgroup\$ – user73941 May 20 '20 at 19:45
  • 1
    \$\begingroup\$ @Milliorn: not as a character array, but as a string new string(slice); \$\endgroup\$ – user73941 May 21 '20 at 4:58
2
\$\begingroup\$

If you want a one-pass, then you can do something like this:

public static string[] Proteins(string strand)
{
    return GetProteins(strand).ToArray();
}

private static IEnumerable<string> GetProteins(string strand)
{
    if (string.IsNullOrEmpty(strand)) { throw new ArgumentNullException(nameof(strand)); }

    for (var i = 0; i < strand.Length; i += 3)
    {
        var condon = strand.Substring(i, Math.Min(3, strand.Length - i));

        if(!TryParseCodon(condon, out string protien)) { break; }

        yield return protien;
    }
}

private static string GetProteinName(string codon)
{
    switch (codon)
    {
        case "UCU":
        case "UCC":
        case "UCA":
        case "UCG":
            return "Serine";
        case "UUU":
        case "UUC":
            return "Phenylalanine";
        case "UUA":
        case "UUG":
            return "Leucine";
        case "UAU":
        case "UAC":
            return "Tyrosine";
        case "UGU":
        case "UGC":
            return "Cysteine";
        case "UGG":
            return "Tryptophan";
        case "AUG":
            return "Methionine";
        default:
            return null;
    }
}

private static bool TryParseCodon(string codon, out string protien)
{
    protien = GetProteinName(codon);
    return protien != null;
}
\$\endgroup\$
  • \$\begingroup\$ private static IEnumerable<string> RnaToCodons(string strand, int k = 0) => strand.ToLookup(c => Math.Floor(k++ / (double)3)).Select(e => new String(e.ToArray())); I guess i'm not seeing it, but what about this did you not like? Is there multiple passes with this? \$\endgroup\$ – Milliorn May 19 '20 at 22:57
  • 1
    \$\begingroup\$ @Milliorn yes, 3 passes (ToLookup, Select, and ToArray()). add that +2 passes on Codons and Proteins. I cut that into 2 passes, one for GetProtiens, and the other is ToArray() which you can element if you just change string[] to IEnumerable<string> or change the return of GetProtiens to string[], but I really encourage using IEnumerable<T> to have more generic approach which would give you access to any type of collection. \$\endgroup\$ – iSR5 May 19 '20 at 23:12
  • \$\begingroup\$ Is a switch with fall-through the only option here? Could we use a hash in C# like in Ruby illustrated in the answers on this question? \$\endgroup\$ – Mast May 20 '20 at 5:51
  • 1
    \$\begingroup\$ @Mast you could converted to HashSet<(string,string[])> but the switch would be faster though. \$\endgroup\$ – iSR5 May 20 '20 at 7:29
  • 1
    \$\begingroup\$ A Dictionary<string, string> would be the obvious choice, but the domain specific nature would suggest there is little benefit in that abstraction. \$\endgroup\$ – VisualMelon May 20 '20 at 8:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.