I'm learning Rust by solving exercises from different tracks on Exercism.
The below code is an implementation of "Protein Translation" from the Python track. I split protein
into separate functions - nucleotide_sequence
and codon_translation
, which I think improves readability.
fn nucleotide_sequence(rna: &str) -> Vec<&str> {
let mut position = 0;
let mut codons = Vec::<&str>::with_capacity(rna.len());
if rna.len() % 3 != 0 {
return codons;
} else {
while position < rna.len() {
let mut sequence_length = 0;
for codon in rna.chars().by_ref().take(3) {
sequence_length += codon.len_utf8();
}
codons.push(&rna[position..position + sequence_length]);
position += sequence_length;
}
codons
}
}
fn codon_translation(sequence_chunks: Vec<&str>) -> Vec<&str> {
let mut polypeptide = Vec::new();
for codon in sequence_chunks.iter() {
match *codon {
"AUG" => polypeptide.push("Methionine"),
"UUU" | "UUC" => polypeptide.push("Phenylalanine"),
"UUA" | "UUG" => polypeptide.push("Leucine"),
"UCU" | "UCC" | "UCA" | "UCG" => polypeptide.push("Serine"),
"UAU" | "UAC" => polypeptide.push("Tyrosine"),
"UGU" | "UGC" => polypeptide.push("Cysteine"),
"UGG" => polypeptide.push("Tryptophan"),
"UAA" | "UAG" | "UGA" => break,
_ => break,
}
}
polypeptide
}
fn proteins(rna: &str) -> Vec<&str> {
let chunks = nucleotide_sequence(rna);
let sequence = codon_translation(chunks);
sequence
}
#[test]
fn test_nucleotide_sequence() {
let data = [
(
"AUG",
vec!["AUG"],
"RNA has one codon (nucleotide sequence)",
),
(
"AUGUUUUCU",
vec!["AUG", "UUU", "UCU"],
"RNA has three codons",
),
(
"XY",
vec![],
"RNA cannot be broken into 3 nucleotide sequences",
),
("", vec![], "RNA doesn't exist; no codons can be formed ..."),
];
for (rna, codon_chunks, description) in data {
assert_eq!(nucleotide_sequence(rna), codon_chunks, "{}", description)
}
}
#[test]
fn test_codon_translation() {
let data = [
(
vec!["UUU", "UUU"],
vec!["Phenylalanine", "Phenylalanine"],
"Codon 'UUUUUU' translates to RNA sequence ['Phenylalanine', 'Phenylalanine']"
),
(
vec!["AUG", "UUU", "UGG"],
vec!["Methionine", "Phenylalanine", "Tryptophan"],
"Codon 'AUGUUUUGG' translates to RNA sequence ['Methionine', 'Phenylalanine', 'Tryptophan']"
),
(
vec!["UAG", "UGG"], vec![], "First codon corresponds to STOP, so others are not taken into consideration"
),
(
vec!["UGG", "UAG"],
vec!["Tryptophan"],
"Codon 'UGGUAG' correponds to only 'Tryptophan'"),
(
vec!["UGG", "UGU", "UAU", "UAA", "UGG", "UUU"],
vec!["Tryptophan", "Cysteine", "Tyrosine"],
"Codon corresponding to STOP is in the middle, whihch makes the RNA sequence '['Tryptophan', 'Cysteine', 'Tyrosine']"
),
(
vec!["ABC", "XYZ"],
vec![],
"Invalid codon, no matching polypeptides found ...",
),
];
for (codon_chunks, protein, description) in data {
assert_eq!(codon_translation(codon_chunks), protein, "{}", description);
}
}
#[test]
fn test_proteins() {
let data = [
(
"AUGUUUUCU",
vec!["Methionine", "Phenylalanine", "Serine"],
"RNA 'AUGUUUUCU' translates to proteins 'Methionine', 'Phenylalanine', 'Serine'",
),
(
"AUGUUUUCUUAAAUG",
vec!["Methionine", "Phenylalanine", "Serine"],
"RNA 'AUGUUUUCUUAAAUG' translates to proteins 'Methionine', 'Phenylalanine', 'Serine'",
),
];
for (codons, polypeptides, description) in data {
assert_eq!(proteins(codons), polypeptides, "{}", description);
}
}
I'm executing this program as a test binary:
rustc --test protein_translation.rs --verbose -o protein_translation_test.exe
.\protein_translation_test.exe
My approach while learning Rust has been to practice test-driven development, write idiomatic code, and (re)learn common programming concepts correctly.
Is there a better way of breaking down the functionality of protein
, solving this problem, or writing tests?