exercism

Exercism - Nucleotide Count

This post shows you how to get Nucleotide Count exercise of Exercism.

Stevinator Stevinator
7 min read
SHARE
exercism dart flutter nucleotide-count

Preparation

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

Nucleotide Count Exercise

So we need to use the following concepts.

Maps

A Map is a collection of key-value pairs. Each key in a map is unique, and you can use keys to look up their corresponding values. Maps can store any type of data as keys and values.

void main() {
  // Map with String keys and int values
  Map<String, int> counts = {
    'A': 3,
    'C': 1,
    'G': 1,
    'T': 2,
  };
  
  // Accessing values
  print(counts['A']); // 3
  
  // Setting values
  counts['A'] = 5;
  print(counts); // {A: 5, C: 1, G: 1, T: 2}
  
  // Creating empty map
  Map<String, int> empty = {};
  empty['A'] = 0;
  print(empty); // {A: 0}
}

String Indexing and Character Access

Strings in Dart can be accessed by index to get individual characters. You can iterate through a string character by character.

void main() {
  String dna = "GATTACA";
  
  // Access character by index
  print(dna[0]); // 'G'
  print(dna[1]); // 'A'
  
  // Get string length
  print(dna.length); // 7
  
  // Iterate through characters
  for (int i = 0; i < dna.length; i++) {
    print(dna[i]);
  }
}

Switch Statements

Switch statements allow you to check a value against multiple cases and execute different code for each case. They’re perfect for handling multiple possible values.

void main() {
  String nucleotide = 'A';
  
  switch (nucleotide) {
    case 'A':
      print("Adenine");
      break;
    case 'C':
      print("Cytosine");
      break;
    case 'G':
      print("Guanine");
      break;
    case 'T':
      print("Thymine");
      break;
    default:
      print("Invalid");
  }
}

Exceptions

Exceptions are used to handle error conditions in Dart. You can throw exceptions when something goes wrong, and catch them to handle the error gracefully.

void main() {
  // Throwing a basic exception
  if (someCondition) {
    throw Exception('Something went wrong');
  }
  
  // Catching exceptions
  try {
    riskyOperation();
  } catch (e) {
    print('Error: $e');
  }
}

Custom Exceptions

You can create custom exceptions by implementing the Exception interface. This allows you to create specific error types for your application.

class InvalidNucleotideException implements Exception {
  final String message;
  InvalidNucleotideException(this.message);
  
  @override
  String toString() => message;
}

void main() {
  // Throw custom exception
  throw InvalidNucleotideException('Invalid nucleotide: X');
}

For Loops

For loops allow you to iterate through a collection or a range of numbers. They’re essential for processing each character in a string.

void main() {
  String text = "ABC";
  
  // Iterate by index
  for (int i = 0; i < text.length; i++) {
    print(text[i]);
  }
  
  // Iterate through characters directly
  for (var char in text.split('')) {
    print(char);
  }
}

Increment Operators

The increment operator (++) increases a variable by 1. It’s commonly used for counting occurrences.

void main() {
  int count = 0;
  
  // Increment
  count++;
  print(count); // 1
  
  // Pre-increment vs post-increment
  int a = 5;
  int b = a++; // b = 5, a = 6
  int c = ++a; // c = 7, a = 7
}

Introduction

Each of us inherits from our biological parents a set of chemical instructions known as DNA that influence how our bodies are constructed. All known life depends on DNA!

Note: You do not need to understand anything about nucleotides or DNA to complete this exercise.

DNA is a long chain of other chemicals and the most important are the four nucleotides, adenine, cytosine, guanine and thymine. A single DNA chain can contain billions of these four nucleotides and the order in which they occur is important! We call the order of these nucleotides in a bit of DNA a “DNA sequence”.

We represent a DNA sequence as an ordered collection of these four nucleotides and a common way to do that is with a string of characters such as “ATTACG” for a DNA sequence of 6 nucleotides. ‘A’ for adenine, ‘C’ for cytosine, ‘G’ for guanine, and ‘T’ for thymine.

Given a string representing a DNA sequence, count how many of each nucleotide is present. If the string contains characters that aren’t A, C, G, or T then it is invalid and you should signal an error.

Examples

  • “GATTACA” → ‘A’: 3, ‘C’: 1, ‘G’: 1, ‘T’: 2
  • “INVALID” → error

What are nucleotides?

Nucleotides are organic molecules that serve as the building blocks of DNA and RNA. The four nucleotides in DNA are:

  • Adenine (A) - Pairs with thymine
  • Cytosine (C) - Pairs with guanine
  • Guanine (G) - Pairs with cytosine
  • Thymine (T) - Pairs with adenine

The sequence of these nucleotides encodes genetic information.

— Biology

How can we count nucleotides?

To count nucleotides in a DNA sequence:

  1. Initialize counters for each nucleotide (A, C, G, T) to 0
  2. Iterate through each character in the DNA string
  3. For each character:
    • If it’s ‘A’, increment the A counter
    • If it’s ‘C’, increment the C counter
    • If it’s ‘G’, increment the G counter
    • If it’s ‘T’, increment the T counter
    • If it’s none of these, throw an exception
  4. Return a map with the counts for each nucleotide

For example, with “GATTACA”:

  • G → G counter = 1
  • A → A counter = 1
  • T → T counter = 1
  • T → T counter = 2
  • A → A counter = 2
  • C → C counter = 1
  • A → A counter = 3
  • Result: {‘A’: 3, ‘C’: 1, ‘G’: 1, ‘T’: 2}

Solution

class InvalidNucleotideException implements Exception {
  final String message;
  InvalidNucleotideException(this.message);
  
  @override
  String toString() => message;
}

class NucleotideCount {
  Map<String, int> count(String strand) {
    int a = 0, c = 0, g = 0, t = 0;
    
    for (int i = 0; i < strand.length; i++) {
      switch (strand[i]) {
        case 'A': a++; break;
        case 'C': c++; break;
        case 'G': g++; break;
        case 'T': t++; break;
        default: 
          throw InvalidNucleotideException('Invalid nucleotide detected: ${strand[i]}');
      }
    }
    
    return {'A': a, 'C': c, 'G': g, 'T': t};
  }
}

Let’s break down the solution:

  1. InvalidNucleotideException - A custom exception class:

    • Implements the Exception interface
    • Stores an error message
    • Overrides toString() to return the message when the exception is converted to a string
  2. NucleotideCount class - Contains the counting logic

  3. count(String strand) - Counts nucleotides in the DNA strand:

    • Initializes counters: int a = 0, c = 0, g = 0, t = 0
    • Iterates through each character using a for loop: for (int i = 0; i < strand.length; i++)
    • Uses a switch statement to handle each possible nucleotide:
      • case 'A': a++; break; - Increments A counter and exits switch
      • case 'C': c++; break; - Increments C counter and exits switch
      • case 'G': g++; break; - Increments G counter and exits switch
      • case 'T': t++; break; - Increments T counter and exits switch
      • default: - Handles any invalid character by throwing InvalidNucleotideException with a descriptive message
    • Returns a map with the counts: {'A': a, 'C': c, 'G': g, 'T': t}

The solution efficiently counts nucleotides using a switch statement for clear, readable code, and properly handles invalid input by throwing a custom exception.


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.