Exercism - Word Count
This post shows you how to get Word Count exercise of Exercism.
Preparation
Before we click on our next exercise, let’s see what concepts of DART we need to consider

So we need to use the following concepts.
Regular Expressions (RegExp)
Regular expressions are patterns used to match character combinations in strings. They’re essential for extracting words from text.
void main() {
// Pattern for words: digits or letters (with optional apostrophe)
RegExp pattern = RegExp(r"\d+|[a-z]+('[a-z]+)?", caseSensitive: false);
String text = "That's the password: 'PASSWORD 123'!";
// Find all matches
Iterable<RegExpMatch> matches = pattern.allMatches(text);
for (var match in matches) {
print(match.group(0)); // That's, the, password, PASSWORD, 123
}
}
allMatches Method
The allMatches method finds all non-overlapping matches of a pattern in a string, returning an iterable of match objects.
void main() {
RegExp pattern = RegExp(r'\d+');
String text = "I have 5 apples and 10 oranges";
Iterable<RegExpMatch> matches = pattern.allMatches(text);
for (var match in matches) {
print(match.group(0)); // 5, 10
}
}
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.
void main() {
Map<String, int> wordCount = {};
// Add or update values
wordCount['hello'] = 1;
wordCount['world'] = 2;
// Access values
print(wordCount['hello']); // 1
// Increment count
wordCount['hello'] = (wordCount['hello'] ?? 0) + 1;
print(wordCount['hello']); // 2
}
Null-Aware Operator
The null-aware operator ?? provides a default value when an expression is null. This is essential for handling cases where a map lookup might return null.
void main() {
Map<String, int> counts = {'hello': 1};
// Get value or default
int count = counts['world'] ?? 0;
print(count); // 0 (key doesn't exist)
// Increment with null safety
counts['hello'] = (counts['hello'] ?? 0) + 1;
print(counts['hello']); // 2
}
String Methods
Strings in Dart have many useful methods. The toLowerCase() method converts all characters to lowercase.
void main() {
String word = "HELLO";
String lower = word.toLowerCase();
print(lower); // "hello"
// Group extraction from regex
RegExpMatch? match = RegExp(r'(\w+)').firstMatch("Hello");
String? group = match?.group(0);
String normalized = group?.toLowerCase() ?? '';
print(normalized); // "hello"
}
For-in Loops
For-in loops allow you to iterate over collections like iterables, lists, or maps.
void main() {
List<String> words = ['hello', 'world', 'hello'];
Map<String, int> counts = {};
// Iterate and count
for (var word in words) {
counts[word] = (counts[word] ?? 0) + 1;
}
print(counts); // {hello: 2, world: 1}
}
Introduction
You teach English as a foreign language to high school students.
You’ve decided to base your entire curriculum on TV shows. You need to analyze which words are used, and how often they’re repeated.
This will let you choose the simplest shows to start with, and to gradually increase the difficulty as time passes.
Instructions
Your task is to count how many times each word occurs in a subtitle of a drama.
The subtitles from these dramas use only ASCII characters.
The characters often speak in casual English, using contractions like they’re or it’s. Though these contractions come from two words (e.g. we are), the contraction (we’re) is considered a single word.
Words can be separated by any form of punctuation (e.g. ”:”, ”!”, or ”?”) or whitespace (e.g. “\t”, “\n”, or ” ”). The only punctuation that does not separate words is the apostrophe in contractions.
Numbers are considered words. If the subtitles say It costs 100 dollars. then 100 will be its own word.
Words are case insensitive. For example, the word you occurs three times in the following sentence:
You come back, you hear me? DO YOU HEAR ME?
The ordering of the word counts in the results doesn’t matter.
Here’s an example that incorporates several of the elements discussed above:
“That’s the password: ‘PASSWORD 123’!”, cried the Special Agent.\nSo I fled.
The mapping for this subtitle would be:
- 123: 1
- agent: 1
- cried: 1
- fled: 1
- i: 1
- password: 2
- so: 1
- special: 1
- that’s: 1
- the: 2
What is word counting?
Word counting is the process of analyzing text to determine how many times each word appears. This is a fundamental task in natural language processing, text analysis, and information retrieval. Word frequency analysis helps identify the most common words, which can be useful for language learning, content analysis, and search engine optimization.
— Natural Language Processing
How can we count words?
To count words in text:
- Extract all words using a regular expression that matches:
- Sequences of digits (numbers)
- Sequences of letters (words)
- Sequences of letters with apostrophes (contractions)
- Convert each word to lowercase for case-insensitive counting
- Count occurrences using a map where keys are words and values are counts
- Return the map of word counts
The key challenge is creating a regex pattern that correctly identifies words while handling contractions and numbers.
⚠️ Old Solution (No Longer Works)
Previously, the solution used forEach and the update method. Here’s what the old solution looked like:
class WordCount {
Map<String,int> countWords(String s){
RegExp regexp = RegExp(r"\d+|[a-z]+('[a-z]+)?",caseSensitive:false);
Map<String,int> result = Map();
regexp.allMatches(s).forEach((m)=> result.update(m.group(0).toLowerCase(),(i)=>i+1,ifAbsent:()=>1));
return result;
}
}
Why This Solution Doesn’t Work Anymore
The old solution has several issues with null safety:
-
m.group(0)can return null: Thegroup(0)method returns aString?(nullable), but the old code doesn’t handle the null case. In null-safe Dart, you cannot pass a potentially null value totoLowerCase()without checking. -
Map()constructor: UsingMap()without type parameters is less explicit and can cause type inference issues in modern Dart. -
updatemethod complexity: Whileupdateworks, the newer approach using null-aware operators is more readable and explicit about handling missing keys. -
forEachvs for-in: Modern Dart style prefersfor-inloops overforEachfor better readability and performance.
The exercise now requires:
- Proper null safety handling using the null-assertion operator
!or null checks - Explicit type annotations for better code clarity
- Using
for-inloops instead offorEachfor better readability - Using null-aware operators (
??) for default values when incrementing counts
Solution
class WordCount {
Map<String, int> countWords(String s) {
final result = <String, int>{};
final pattern = RegExp(r"\d+|[a-z]+('[a-z]+)?", caseSensitive: false);
for (final match in pattern.allMatches(s)) {
final word = match.group(0)!.toLowerCase();
result[word] = (result[word] ?? 0) + 1;
}
return result;
}
}
Let’s break down the solution:
-
final result = <String, int>{}- Creates an empty map to store word counts with explicit type annotation -
final pattern = RegExp(r"\d+|[a-z]+('[a-z]+)?", caseSensitive: false)- Defines the regex pattern:\d+- Matches one or more digits (numbers)|- OR operator[a-z]+- Matches one or more letters('[a-z]+)?- Optionally matches an apostrophe followed by one or more letters (for contractions)caseSensitive: false- Makes the pattern case-insensitive
-
for (final match in pattern.allMatches(s))- Iterates through all matches found in the input string:allMatches(s)finds all non-overlapping matches- Each
matchis aRegExpMatchobject
-
final word = match.group(0)!.toLowerCase()- Extracts the matched word:match.group(0)returns the entire match (the word)!is the null-assertion operator (we know it won’t be null because we found a match)toLowerCase()converts to lowercase for case-insensitive counting
-
result[word] = (result[word] ?? 0) + 1- Increments the count:result[word]gets the current count (or null if the word hasn’t been seen)?? 0provides a default value of 0 if the word is new+ 1increments the count- Assigns the new count back to the map
The solution efficiently extracts words using regex, handles null safety properly, and counts occurrences using a map with null-aware operators.
You can watch this tutorial on YouTube. So don’t forget to like and subscribe. 😉
Watch on YouTube