exercism

Exercism - Protein Translation

This post shows you how to get Protein Translation exercise of Exercism.

Stevinator Stevinator
11 min read
SHARE
exercism dart flutter protein-translation

Preparation

Before we click on our next exercise, let’s see what concepts of DART we need to consider

Protein Translation Exercise

So we need to use the following concepts.

Classes

Classes define blueprints for objects. They can contain methods, fields, and constants that work together to solve a problem.

class ProteinTranslation {
  // Class methods and fields go here
  List<String> translate(String rna) {
    // Translation logic
    return [];
  }
}

void main() {
  // Use class methods
  ProteinTranslation translator = ProteinTranslation();
  List<String> proteins = translator.translate('AUGUUUUCU');
  print(proteins); // ['Methionine', 'Phenylalanine', 'Serine']
}

Static Const Maps

Static const maps are class-level constants that can be accessed without creating an instance. They’re perfect for lookup tables like codon-to-amino-acid mappings.

class ProteinTranslation {
  // Static const map - shared by all instances
  static const _codons = {
    'AUG': 'Methionine',
    'UUU': 'Phenylalanine',
    'UUC': 'Phenylalanine',
  };
  
  // Access without instance
  String? lookup(String codon) {
    return _codons[codon];
  }
}

void main() {
  // Access static const map
  String? aminoAcid = ProteinTranslation._codons['AUG'];
  print(aminoAcid); // Methionine
}

Lists

Lists are ordered collections of elements. You can add elements, check length, and iterate through them.

void main() {
  // Create empty list
  List<String> proteins = [];
  
  // Add elements
  proteins.add('Methionine');
  proteins.add('Phenylalanine');
  print(proteins); // [Methionine, Phenylalanine]
  
  // Check if empty
  print(proteins.isEmpty); // false
  print(proteins.isNotEmpty); // true
  
  // Get length
  print(proteins.length); // 2
}

String substring()

The substring() method extracts a portion of a string. It takes a start index and optionally an end index. It’s perfect for extracting codons (3-character sequences) from RNA strings.

void main() {
  String rna = "AUGUUUUCU";
  
  // Get substring with start and end index
  String codon1 = rna.substring(0, 3);
  print(codon1); // "AUG"
  
  String codon2 = rna.substring(3, 6);
  print(codon2); // "UUU"
  
  // Extract codons in a loop
  for (int i = 0; i < rna.length; i += 3) {
    if (i + 3 <= rna.length) {
      String codon = rna.substring(i, i + 3);
      print(codon); // AUG, UUU, UCU
    }
  }
}

For Loops with Step

For loops can increment by values other than 1 using compound assignment operators. This is perfect for processing strings in chunks (like 3-character codons).

void main() {
  String rna = "AUGUUUUCU";
  
  // Loop with step of 3
  for (int i = 0; i < rna.length; i += 3) {
    print('Index: $i');
    // Index: 0, 3, 6
  }
  
  // Extract codons
  for (int i = 0; i < rna.length; i += 3) {
    if (i + 3 <= rna.length) {
      String codon = rna.substring(i, i + 3);
      print(codon); // AUG, UUU, UCU
    }
  }
}

Map containsKey()

The containsKey() method checks if a map contains a specific key. It’s useful for validating that a codon exists in the translation table.

void main() {
  Map<String, String> codons = {
    'AUG': 'Methionine',
    'UUU': 'Phenylalanine',
  };
  
  // Check if key exists
  if (codons.containsKey('AUG')) {
    print('Found AUG');
  }
  
  if (!codons.containsKey('XYZ')) {
    print('XYZ not found');
  }
  
  // Use for validation
  String codon = 'AUG';
  if (!codons.containsKey(codon)) {
    throw ArgumentError('Invalid codon: $codon');
  }
}

Map Lookup with Null Assertion (!)

When you’re certain a map key exists, you can use the null assertion operator (!) to tell Dart that the value won’t be null. This is safe after checking with containsKey().

void main() {
  Map<String, String> codons = {
    'AUG': 'Methionine',
    'UUU': 'Phenylalanine',
  };
  
  // Null assertion operator
  String aminoAcid = codons['AUG']!;
  print(aminoAcid); // Methionine
  
  // Safe after containsKey check
  String codon = 'AUG';
  if (codons.containsKey(codon)) {
    String aminoAcid = codons[codon]!; // Safe to use !
    print(aminoAcid); // Methionine
  }
}

ArgumentError

ArgumentError is thrown when a function receives an invalid argument. You can throw it to indicate that the input doesn’t meet the function’s requirements.

void main() {
  void translate(String rna) {
    // Validate input
    if (rna.isEmpty) {
      throw ArgumentError('RNA sequence cannot be empty');
    }
    
    if (rna.length % 3 != 0) {
      throw ArgumentError('Invalid RNA sequence length');
    }
    
    // Process...
  }
  
  try {
    translate('AUG'); // OK
    translate('AU'); // Throws ArgumentError
  } catch (e) {
    print(e); // ArgumentError: Invalid RNA sequence length
  }
}

Break Statement

The break statement exits a loop immediately. It’s useful for stopping processing when a STOP codon is encountered.

void main() {
  List<String> codons = ['AUG', 'UUU', 'UAA', 'UCU'];
  List<String> proteins = [];
  
  for (String codon in codons) {
    if (codon == 'UAA') {
      break; // Exit loop immediately
    }
    proteins.add('Protein');
  }
  
  print(proteins); // [Protein, Protein] - stops before UAA
}

String isEmpty and isNotEmpty

The isEmpty property checks if a string has no characters. isNotEmpty is the opposite. They’re useful for validating input.

void main() {
  String empty = '';
  String notEmpty = 'AUG';
  
  // Check if empty
  print(empty.isEmpty); // true
  print(notEmpty.isEmpty); // false
  
  // Check if not empty
  print(empty.isNotEmpty); // false
  print(notEmpty.isNotEmpty); // true
  
  // Use for validation
  String rna = '';
  if (rna.isEmpty) {
    return []; // Return empty list
  }
}

String length

The length property returns the number of characters in a string. It’s essential for bounds checking when extracting substrings.

void main() {
  String rna = "AUGUUUUCU";
  
  // Get length
  print(rna.length); // 9
  
  // Check bounds before substring
  int i = 6;
  if (i + 3 <= rna.length) {
    String codon = rna.substring(i, i + 3);
    print(codon); // UCU
  } else {
    print('Not enough characters');
  }
}

Comparison Operators

Comparison operators (==, !=) compare values and return boolean results. They’re used to check if a codon is a STOP codon.

void main() {
  String aminoAcid = 'STOP';
  
  // Equality check
  if (aminoAcid == 'STOP') {
    print('Stop translation');
  }
  
  // Not equal
  if (aminoAcid != 'STOP') {
    print('Continue translation');
  }
  
  // Use in translation
  String translation = 'STOP';
  if (translation == 'STOP') {
    break; // Stop processing
  } else {
    proteins.add(translation); // Add to list
  }
}

Introduction

Your job is to translate RNA sequences into proteins.

RNA strands are made up of three-nucleotide sequences called codons. Each codon translates to an amino acid. When joined together, those amino acids make a protein.

In the real world, there are 64 codons, which in turn correspond to 20 amino acids. However, for this exercise, you’ll only use a few of the possible 64. They are listed below:

CodonAmino Acid
AUGMethionine
UUU, UUCPhenylalanine
UUA, UUGLeucine
UCU, UCC, UCA, UCGSerine
UAU, UACTyrosine
UGU, UGCCysteine
UGGTryptophan
UAA, UAG, UGASTOP

Example

For example, the RNA string “AUGUUUUCU” has three codons: “AUG”, “UUU” and “UCU”. These map to Methionine, Phenylalanine, and Serine.

”STOP” Codons

You’ll note from the table above that there are three “STOP” codons. If you encounter any of these codons, ignore the rest of the sequence — the protein is complete.

For example, “AUGUUUUCUUAAAUG” contains a STOP codon (“UAA”). Once we reach that point, we stop processing. We therefore only consider the part before it (i.e. “AUGUUUUCU”), not any further codons after it (i.e. “AUG”).

Instructions

Your task is to translate RNA sequences into proteins.

How can we translate RNA to proteins?

To translate RNA sequences into proteins:

  1. Create codon map: Map each codon to its corresponding amino acid (including STOP codons)
  2. Handle empty input: Return empty list if RNA string is empty
  3. Process in chunks of 3: Extract codons by taking 3 characters at a time
  4. Validate sequence: Check that each codon exists in the map
  5. Translate codon: Look up the amino acid for each codon
  6. Stop on STOP: If the amino acid is “STOP”, break the loop immediately
  7. Collect proteins: Add each amino acid to the result list
  8. Return result: Return the list of amino acids

The key insight is processing the RNA string in chunks of 3 characters (codons), looking up each codon in a translation table, and stopping immediately when a STOP codon is encountered.

For example, with RNA “AUGUUUUCUUAA”:

  • Extract “AUG” → Methionine → Add to list
  • Extract “UUU” → Phenylalanine → Add to list
  • Extract “UCU” → Serine → Add to list
  • Extract “UAA” → STOP → Break loop
  • Result: [‘Methionine’, ‘Phenylalanine’, ‘Serine’]

Solution

class ProteinTranslation {
  static const _m = {
    'AUG': 'Methionine', 'UUU': 'Phenylalanine', 'UUC': 'Phenylalanine',
    'UUA': 'Leucine', 'UUG': 'Leucine', 'UCU': 'Serine', 'UCC': 'Serine',
    'UCA': 'Serine', 'UCG': 'Serine', 'UAU': 'Tyrosine', 'UAC': 'Tyrosine',
    'UGU': 'Cysteine', 'UGC': 'Cysteine', 'UGG': 'Tryptophan',
    'UAA': 'STOP', 'UAG': 'STOP', 'UGA': 'STOP',
  };

  List<String> translate(String rna) {
    if (rna.isEmpty) return [];

    final p = <String>[];

    for (var i = 0; i < rna.length; i += 3) {
      if (i + 3 > rna.length) throw ArgumentError('Invalid RNA sequence length');

      final c = rna.substring(i, i + 3);

      if (!_m.containsKey(c)) throw ArgumentError('Invalid codon: $c');

      final t = _m[c]!;

      if (t == 'STOP') break;

      p.add(t);
    }

    return p;
  }
}

Let’s break down the solution:

  1. class ProteinTranslation - Translation class:

    • Encapsulates the protein translation logic
    • Contains the codon-to-amino-acid mapping
  2. static const _m - Codon map:

    • Static const map shared by all instances
    • Maps each codon to its amino acid
    • Includes all codons and their translations
    • Includes STOP codons (UAA, UAG, UGA)
  3. List<String> translate(String rna) - Translation method:

    • Takes an RNA string as input
    • Returns a list of amino acid names
    • Processes the RNA string codon by codon
  4. if (rna.isEmpty) return [] - Handle empty input:

    • Returns empty list immediately if RNA string is empty
    • Early return for edge case
  5. final p = <String>[] - Result list:

    • Creates an empty list to store amino acids
    • Will be populated as we translate codons
  6. for (var i = 0; i < rna.length; i += 3) - Loop through codons:

    • Iterates through RNA string in steps of 3
    • i starts at 0, increments by 3 each iteration
    • Processes each 3-character codon
  7. if (i + 3 > rna.length) - Validate sequence length:

    • Checks if there are enough characters for a complete codon
    • Throws ArgumentError if sequence is incomplete
    • Prevents index out of bounds errors
  8. final c = rna.substring(i, i + 3) - Extract codon:

    • Extracts 3 characters starting at index i
    • Creates a codon string (e.g., “AUG”, “UUU”)
  9. if (!_m.containsKey(c)) - Validate codon:

    • Checks if the codon exists in the translation map
    • Throws ArgumentError with descriptive message if invalid
    • Ensures only valid codons are processed
  10. final t = _m[c]! - Look up amino acid:

    • Retrieves the amino acid for the codon
    • Uses null assertion operator (!) since we’ve validated with containsKey()
    • Returns either an amino acid name or “STOP”
  11. if (t == 'STOP') break - Stop on STOP codon:

    • Checks if the translation is “STOP”
    • Immediately exits the loop if STOP codon encountered
    • Ignores any remaining codons in the sequence
  12. p.add(t) - Add amino acid:

    • Adds the amino acid to the result list
    • Only executed if not a STOP codon
  13. return p - Return result:

    • Returns the list of translated amino acids
    • Empty if input was empty or only STOP codons were found

The solution efficiently translates RNA sequences into proteins by processing codons in chunks of 3, validating each codon, and stopping immediately when a STOP codon is encountered.


A video tutorial for this exercise is coming soon! In the meantime, check out my YouTube channel for more Dart and Flutter tutorials. 😉

Visit My YouTube Channel
Stevinator

Stevinator

Stevinator is a software engineer passionate about clean code and best practices. Loves sharing knowledge with the developer community.