exercism

Exercism - Bottle Song

This post shows you how to get Bottle Song exercise of Exercism.

Stevinator Stevinator
9 min read
SHARE
exercism dart flutter bottle-song

Preparation

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

Bottle Song Exercise

So we need to use the following concepts.

Lists

Lists are ordered collections of items. You can add elements, add multiple elements at once, and build lists dynamically.

void main() {
  // Create empty list
  List<String> lyrics = <String>[];
  
  // Add single element
  lyrics.add('Line 1');
  
  // Add multiple elements
  lyrics.addAll(['Line 2', 'Line 3', 'Line 4']);
  
  print(lyrics); // [Line 1, Line 2, Line 3, Line 4]
  
  // Add empty string for spacing
  lyrics.add('');
  lyrics.add('Line 5');
  print(lyrics); // [Line 1, Line 2, Line 3, Line 4, , Line 5]
}

For Loops

For loops allow you to iterate a specific number of times. They’re perfect for generating repetitive content.

void main() {
  List<String> verses = [];
  
  // Loop from 0 to takeDown - 1
  int takeDown = 3;
  for (int i = 0; i < takeDown; i++) {
    int current = 10 - i; // 10, 9, 8
    verses.add('Verse for $current bottles');
  }
  
  print(verses);
  // [Verse for 10 bottles, Verse for 9 bottles, Verse for 8 bottles]
}

String Interpolation

String interpolation allows you to embed expressions and variables directly within strings using ${expression} or $variable.

void main() {
  int current = 5;
  String word = 'bottles';
  
  // Basic interpolation
  String line = '$current green $word hanging on the wall,';
  print(line); // "5 green bottles hanging on the wall,"
  
  // Expression interpolation
  int next = current - 1;
  String line2 = "There'll be $next green $word hanging on the wall.";
  print(line2); // "There'll be 4 green bottles hanging on the wall."
}

Helper Methods

Helper methods (often private methods starting with _) encapsulate reusable logic, making code more organized and maintainable.

class BottleSong {
  // Public method
  List<String> recite(int start, int takeDown) {
    // Uses helper methods
    String word = _bottleWord(5);
    String number = _numberToWord(5);
    return [];
  }
  
  // Private helper method
  String _bottleWord(int n) {
    return n == 1 ? 'bottle' : 'bottles';
  }
  
  // Another helper method
  String _numberToWord(int n) {
    return 'five';
  }
}

Named Parameters with Required

Named parameters allow you to pass arguments by name. The required keyword makes a named parameter mandatory.

void main() {
  String format(int n, {required bool capitalize}) {
    String word = 'five';
    return capitalize ? word.toUpperCase() : word;
  }
  
  // Must provide capitalize parameter
  String result1 = format(5, capitalize: true); // "FIVE"
  String result2 = format(5, capitalize: false); // "five"
  
  // This would cause an error:
  // String result3 = format(5); // Error: required parameter missing
}

String Methods

Strings have many useful methods. toUpperCase() converts a character to uppercase, and substring() extracts part of a string.

void main() {
  String word = 'five';
  
  // Get first character and uppercase it
  String first = word[0].toUpperCase();
  print(first); // 'F'
  
  // Get substring from index 1 to end
  String rest = word.substring(1);
  print(rest); // 'ive'
  
  // Capitalize first letter
  String capitalized = word[0].toUpperCase() + word.substring(1);
  print(capitalized); // 'Five'
}

Conditional Logic

Conditional statements allow you to execute different code based on conditions. This is essential for handling special cases.

void main() {
  int n = 1;
  
  // Check for singular/plural
  String bottle = n == 1 ? 'bottle' : 'bottles';
  print(bottle); // 'bottle'
  
  // Add spacing conditionally
  List<String> lyrics = [];
  int i = 1;
  if (i > 0) {
    lyrics.add(''); // Empty line between verses
  }
  
  // Calculate next value
  int current = 5;
  int next = current - 1;
  print(next); // 4
}

Constants

Constants are values that don’t change. The const keyword creates compile-time constants.

void main() {
  // Constant list
  const words = [
    'no', 'one', 'two', 'three', 'four', 'five',
    'six', 'seven', 'eight', 'nine', 'ten'
  ];
  
  // Access by index
  print(words[0]); // 'no'
  print(words[5]); // 'five'
  print(words[10]); // 'ten'
  
  // Use in calculations
  int n = 5;
  String word = words[n];
  print(word); // 'five'
}

Introduction

Recite the lyrics to that popular children’s repetitive song: Ten Green Bottles.

Note that not all verses are identical.

Instructions

The song follows a pattern where each verse counts down from a starting number. Each verse has four lines:

  1. “[Number] green [bottle/bottles] hanging on the wall,”
  2. “[Number] green [bottle/bottles] hanging on the wall,”
  3. “And if one green bottle should accidentally fall,”
  4. “There’ll be [next number] green [bottle/bottles] hanging on the wall.”

The song continues counting down until reaching zero. Special cases:

  • When the number is 1, use “bottle” (singular) instead of “bottles” (plural)
  • When the next number is 0, use “no” instead of “zero”
  • The first word of each line should be capitalized when it starts a verse

Example Verses

Ten bottles:

Ten green bottles hanging on the wall,
Ten green bottles hanging on the wall,
And if one green bottle should accidentally fall,
There'll be nine green bottles hanging on the wall.

One bottle:

One green bottle hanging on the wall,
One green bottle hanging on the wall,
And if one green bottle should accidentally fall,
There'll be no green bottles hanging on the wall.

What is the Bottle Song?

“Ten Green Bottles” is a children’s counting song that helps teach subtraction. The song counts down from ten bottles to zero, with each verse describing one bottle falling. It’s similar to “99 Bottles of Beer” but designed for younger children. The repetitive nature makes it easy to learn and helps with number recognition and counting backwards.

— Children’s Songs

How can we generate the lyrics?

To generate the lyrics:

  1. Loop through verses: For each verse from startBottles down to startBottles - takeDown + 1
  2. Calculate current and next: Current number decreases each iteration, next is current - 1
  3. Add spacing: Add an empty line between verses (except before the first)
  4. Build each verse: Create four lines per verse:
    • Two lines with current number (capitalized)
    • One line with “And if one green bottle should accidentally fall,”
    • One line with next number (not capitalized)
  5. Handle special cases:
    • Use “bottle” for 1, “bottles” for other numbers
    • Use “no” for 0
    • Capitalize first word when starting a verse

The key insight is using helper methods to convert numbers to words and handle singular/plural, then building verses in a loop.

Solution

class BottleSong {
  List<String> recite(int startBottles, int takeDown) {
    final lyrics = <String>[];

    for (int i = 0; i < takeDown; i++) {
      final current = startBottles - i;
      final next = current - 1;

      if (i > 0) {
        lyrics.add(''); // Empty line between verses
      }

      lyrics.addAll([
        '${_numberToWord(current, capitalize: true)} green ${_bottleWord(current)} hanging on the wall,',
        '${_numberToWord(current, capitalize: true)} green ${_bottleWord(current)} hanging on the wall,',
        'And if one green bottle should accidentally fall,',
        "There'll be ${_numberToWord(next, capitalize: false)} green ${_bottleWord(next)} hanging on the wall."
      ]);
    }

    return lyrics;
  }

  String _numberToWord(int n, {required bool capitalize}) {
    const words = [
      'no', 'one', 'two', 'three', 'four', 'five',
      'six', 'seven', 'eight', 'nine', 'ten'
    ];
    final word = words[n];
    return capitalize ? word[0].toUpperCase() + word.substring(1) : word;
  }

  String _bottleWord(int n) => n == 1 ? 'bottle' : 'bottles';
}

Let’s break down the solution:

  1. List<String> recite(int startBottles, int takeDown) - Main method that generates lyrics:

    • Takes starting number of bottles and how many verses to generate
    • Returns a list of strings (all lines of all verses)
  2. final lyrics = <String>[] - Initialize empty list:

    • Uses type inference with <String>[] syntax
    • Will contain all lines from all verses
  3. for (int i = 0; i < takeDown; i++) - Loop to generate verses:

    • Iterates takeDown times
    • i represents the verse index (0, 1, 2, …)
  4. final current = startBottles - i - Calculate current bottle count:

    • Decreases with each iteration
    • Example: startBottles=10, i=0 → current=10; i=1 → current=9
  5. final next = current - 1 - Calculate next bottle count:

    • One less than current
    • Example: current=10 → next=9; current=1 → next=0
  6. if (i > 0) lyrics.add('') - Add spacing between verses:

    • Adds empty string before each verse except the first
    • Creates visual separation between verses
  7. lyrics.addAll([...]) - Add all four lines of the verse:

    • Line 1 & 2: Current number (capitalized) with bottle/bottles
    • Line 3: Fixed text “And if one green bottle should accidentally fall,”
    • Line 4: Next number (not capitalized) with bottle/bottles
  8. String _numberToWord(int n, {required bool capitalize}) - Helper to convert number to word:

    • const words = [...]: Constant list mapping index to word (0=‘no’, 1=‘one’, etc.)
    • final word = words[n]: Get the word for number n
    • Capitalization logic: If capitalize is true, uppercase first letter and concatenate with rest
    • Returns “Five” if capitalize=true, “five” if capitalize=false
  9. String _bottleWord(int n) => n == 1 ? 'bottle' : 'bottles' - Helper for singular/plural:

    • Uses expression-bodied method
    • Returns “bottle” for 1, “bottles” for all other numbers (including 0)

The solution efficiently generates the repetitive song lyrics by using helper methods for number-to-word conversion and singular/plural handling, then building verses in a loop with proper spacing.


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.