exercism

Exercism - Phone Number

This post shows you how to get Phone Number exercise of Exercism.

Stevinator Stevinator
10 min read
SHARE
exercism dart flutter phone-number

Preparation

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

Phone Number Exercise

So we need to use the following concepts.

FormatException

FormatException is thrown when a string or other data does not have the expected format. It’s useful for validation errors.

void main() {
  void validate(String input) {
    if (input.contains('a')) {
      throw FormatException('letters not permitted');
    }
    if (input.length < 10) {
      throw FormatException('must not be fewer than 10 digits');
    }
  }
  
  try {
    validate('abc123');
  } catch (e) {
    print(e); // FormatException: letters not permitted
  }
}

String Contains with RegExp

The contains() method can check if a string contains a pattern matching a regular expression. It’s useful for detecting invalid characters.

void main() {
  String number = "1-800-CALL-NOW";
  
  // Check for letters
  if (number.contains(RegExp(r'[a-zA-Z]'))) {
    print('Contains letters');
  }
  
  // Check for digits
  if (number.contains(RegExp(r'[0-9]'))) {
    print('Contains digits');
  }
  
  // Pattern breakdown:
  // [a-zA-Z] - character class matching any letter (lowercase or uppercase)
}

String ReplaceAll with RegExp

The replaceAll() method can use regular expressions to remove or replace patterns. Character classes and negated character classes are useful for this.

void main() {
  String number = "+1 (613)-995-0253";
  
  // Remove all non-digits
  String digits = number.replaceAll(RegExp(r'[^0-9]'), '');
  print(digits); // "16139950253"
  
  // Pattern breakdown:
  // [^0-9] - negated character class: anything that is NOT a digit
  // This removes all non-digit characters
  
  // Remove specific characters
  String cleaned = number
      .replaceAll(RegExp(r'[0-9]'), '')  // Remove digits
      .replaceAll(RegExp(r'\s'), '')     // Remove whitespace
      .replaceAll('+', '')                // Remove plus
      .replaceAll('-', '');               // Remove dash
  print(cleaned); // "( )-."
}

String isEmpty and isNotEmpty

The isEmpty property returns true if the string has no characters, and isNotEmpty returns true if it has at least one character.

void main() {
  String empty = '';
  String notEmpty = 'abc';
  
  // 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 cleaned = "abc";
  if (cleaned.isNotEmpty) {
    throw FormatException('Invalid characters found');
  }
}

String Indexing

You can access individual characters in a string using square brackets with an index. The first character is at index 0.

void main() {
  String number = "6139950253";
  
  // Access by index
  print(number[0]); // '6' (first digit - area code start)
  print(number[3]); // '9' (fourth digit - exchange code start)
  
  // Validate specific positions
  if (number[0] == '0') {
    print('Area code cannot start with zero');
  }
  if (number[3] == '1') {
    print('Exchange code cannot start with one');
  }
}

String Substring Method

The substring() method extracts a portion of a string. It’s useful for removing prefixes like country codes.

void main() {
  String number = "16139950253";
  
  // Get substring from index 1 to end
  String withoutCountry = number.substring(1);
  print(withoutCountry); // "6139950253"
  
  // Get substring with start and end
  String areaCode = number.substring(1, 4);
  print(areaCode); // "613"
  
  // Remove country code if present
  if (number.length == 11 && number[0] == '1') {
    number = number.substring(1);
  }
}

Conditional Logic

Conditional statements allow you to execute different code based on conditions. This is essential for validation and error handling.

void main() {
  String digits = "6139950253";
  
  // Validate length
  if (digits.length < 10) {
    throw FormatException('too short');
  }
  if (digits.length > 11) {
    throw FormatException('too long');
  }
  
  // Validate specific positions
  if (digits[0] == '0') {
    throw FormatException('area code cannot start with zero');
  }
  if (digits[0] == '1') {
    throw FormatException('area code cannot start with one');
  }
}

Regular Expressions

Regular expressions allow you to match patterns in strings. Character classes [] match any character within, and ^ at the start negates the class.

void main() {
  // Character class: matches any digit
  RegExp digits = RegExp(r'[0-9]');
  
  // Negated character class: matches anything NOT a digit
  RegExp nonDigits = RegExp(r'[^0-9]');
  
  // Character class: matches any letter
  RegExp letters = RegExp(r'[a-zA-Z]');
  
  // Whitespace
  RegExp whitespace = RegExp(r'\s');
  
  // Use in replaceAll
  String number = "+1 (613)-995-0253";
  String digitsOnly = number.replaceAll(RegExp(r'[^0-9]'), '');
  print(digitsOnly); // "16139950253"
}

Introduction

You’ve joined LinkLine, a leading communications company working to ensure reliable connections for everyone. The team faces a big challenge: users submit phone numbers in all sorts of formats — dashes, spaces, dots, parentheses, and even prefixes. Some numbers are valid, while others are impossible to use.

Your mission is to turn this chaos into order. You’ll clean up valid numbers, formatting them appropriately for use in the system. At the same time, you’ll identify and filter out any invalid entries.

The success of LinkLine’s operations depends on your ability to separate the useful from the unusable. Are you ready to take on the challenge and keep the connections running smoothly?

Instructions

Clean up phone numbers so that they can be sent SMS messages.

The North American Numbering Plan (NANP) is a telephone numbering system used by many countries in North America like the United States, Canada or Bermuda. All NANP-countries share the same international country code: 1.

NANP numbers are ten-digit numbers consisting of a three-digit Numbering Plan Area code, commonly known as area code, followed by a seven-digit local number. The first three digits of the local number represent the exchange code, followed by the unique four-digit number which is the subscriber number.

The format is usually represented as:

NXX NXX-XXXX

where N is any digit from 2 through 9 and X is any digit from 0 through 9.

Sometimes they also have the country code (represented as 1 or +1) prefixed.

Your task is to clean up differently formatted telephone numbers by removing punctuation and the country code if present.

Examples

For example, the inputs:

  • +1 (613)-995-0253
  • 613-995-0253
  • 1 613 995 0253
  • 613.995.0253

should all produce the output:

6139950253

Note

As this exercise only deals with telephone numbers used in NANP-countries, only 1 is considered a valid country code.

What is the North American Numbering Plan?

The North American Numbering Plan (NANP) is a telephone numbering plan that encompasses 25 distinct regions in twenty countries primarily in North America, including the Caribbean and some Atlantic and Pacific islands. The NANP uses a standardized format: a three-digit area code, followed by a three-digit exchange code, and finally a four-digit subscriber number.

— Wikipedia

How can we clean and validate phone numbers?

To clean and validate phone numbers:

  1. Check for invalid characters: Reject numbers containing letters
  2. Check for invalid punctuation: Only allow digits, spaces, +, -, ., and parentheses
  3. Extract digits: Remove all non-digit characters
  4. Validate length: Must be 10 or 11 digits
  5. Handle country code: If 11 digits, must start with 1, then remove it
  6. Validate area code: First digit (after removing country code) must be 2-9
  7. Validate exchange code: Fourth digit must be 2-9

The key insight is using multiple validation steps to ensure the phone number is properly formatted and follows NANP rules.

For example, with “+1 (613)-995-0253”:

  • Check letters: none ✓
  • Check punctuation: valid ✓
  • Extract digits: “16139950253”
  • Length: 11 digits ✓
  • Country code: starts with 1 ✓
  • Remove country code: “6139950253”
  • Area code: starts with 6 (2-9) ✓
  • Exchange code: starts with 9 (2-9) ✓
  • Result: “6139950253”

Solution

class PhoneNumber {
  String clean(String number) {
    // Check for letters
    if (number.contains(RegExp(r'[a-zA-Z]'))) {
      throw FormatException('letters not permitted');
    }
    
    // Check for invalid punctuation
    // Valid: digits (0-9), whitespace (\s), plus (+), dash (-), dot (.), parentheses ()
    // Remove all valid characters and see if anything is left
    final withoutValidChars = number
        .replaceAll(RegExp(r'[0-9]'), '') // remove digits
        .replaceAll(RegExp(r'\s'), '')     // remove whitespace
        .replaceAll('+', '')                // remove plus
        .replaceAll('-', '')                // remove dash
        .replaceAll('.', '')                // remove dot
        .replaceAll('(', '')                // remove open paren
        .replaceAll(')', '');               // remove close paren
    
    if (withoutValidChars.isNotEmpty) {
      throw FormatException('punctuations not permitted');
    }
    
    // Extract only digits
    final digits = number.replaceAll(RegExp(r'[^0-9]'), '');
    
    // Validate length
    if (digits.length < 10) {
      throw FormatException('must not be fewer than 10 digits');
    }
    if (digits.length > 11) {
      throw FormatException('must not be greater than 11 digits');
    }
    
    // Handle country code
    String cleanNumber = digits;
    if (digits.length == 11) {
      if (digits[0] != '1') {
        throw FormatException('11 digits must start with 1');
      }
      cleanNumber = digits.substring(1);
    }
    
    // Validate area code
    if (cleanNumber[0] == '0') {
      throw FormatException('area code cannot start with zero');
    }
    if (cleanNumber[0] == '1') {
      throw FormatException('area code cannot start with one');
    }
    
    // Validate exchange code
    if (cleanNumber[3] == '0') {
      throw FormatException('exchange code cannot start with zero');
    }
    if (cleanNumber[3] == '1') {
      throw FormatException('exchange code cannot start with one');
    }
    
    return cleanNumber;
  }
}

Let’s break down the solution:

  1. String clean(String number) - Main method that cleans and validates phone numbers:

    • Takes a phone number string (may contain formatting)
    • Returns cleaned 10-digit number
    • Throws FormatException for invalid inputs
  2. Letter validation:

    • if (number.contains(RegExp(r'[a-zA-Z]'))): Checks if string contains any letters
    • throw FormatException('letters not permitted'): Throws error if letters found
    • Phone numbers should only contain digits and allowed punctuation
  3. Punctuation validation:

    • withoutValidChars: Removes all valid characters (digits, spaces, +, -, ., parentheses)
    • Uses multiple replaceAll() calls to strip valid characters
    • if (withoutValidChars.isNotEmpty): If anything remains, it’s invalid punctuation
    • Throws error for invalid punctuation characters
  4. Extract digits:

    • final digits = number.replaceAll(RegExp(r'[^0-9]'), ''): Removes all non-digit characters
    • [^0-9]: Negated character class - matches anything that is NOT a digit
    • Results in a string containing only digits
  5. Length validation:

    • if (digits.length < 10): Must have at least 10 digits
    • if (digits.length > 11): Must have at most 11 digits
    • Throws appropriate errors for invalid lengths
  6. Country code handling:

    • if (digits.length == 11): If 11 digits, check for country code
    • if (digits[0] != '1'): Country code must be 1
    • cleanNumber = digits.substring(1): Remove country code, keep remaining 10 digits
  7. Area code validation:

    • if (cleanNumber[0] == '0'): Area code cannot start with 0
    • if (cleanNumber[0] == '1'): Area code cannot start with 1
    • Area code must start with 2-9 (N in NXX format)
  8. Exchange code validation:

    • if (cleanNumber[3] == '0'): Exchange code (4th digit) cannot start with 0
    • if (cleanNumber[3] == '1'): Exchange code cannot start with 1
    • Exchange code must start with 2-9 (N in NXX format)
  9. return cleanNumber - Return the cleaned number:

    • Returns the validated 10-digit phone number

The solution thoroughly validates phone numbers by checking for invalid characters, validating length, handling country codes, and ensuring area and exchange codes follow NANP rules.


You can watch this tutorial on YouTube. So don’t forget to like and subscribe. 😉

Watch on YouTube
Stevinator

Stevinator

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