Dart Coding Challenge #4: Longest Word
Practice for loops, do-while loops and string manipulation
Welcome to Dart Coding Challenge #4! In this challenge, we will be writing a Dart program that prompts the user for a body of text and determines the longest word in the text and returns it to the user.
Project Specifications
Here are the project specifications for the Longest Word coding challenge:
Obtain a body of text from the user via the command line.
The text must include ONLY alphanumeric characters.
The text must include at least three words and 1 sentence.
If any of the above criteria are not met, prompt the user for input again with a message indicating the problem.
Once you have valid input, your job is to figure out the longest word in the body of text and display back to the user which word is the longest and how many characters it is.
If there are two words of the same length, return the last one that occurs in the sentence!
Solution Approach
To solve this problem, we will follow these general steps:
Obtain a body of text from the user via the command line.
Validate that the text meets the specified criteria.
If the text is valid, determine the longest word in the text.
Display the longest word and its length back to the user.
We will use regular expressions and lists to make this process simpler.
Solution 1
Let’s take a close look at solution 1.
We used the dart create
command-line program to create our project, which leaves us with three folders to focus on. /bin, /lib and /test.
/bin
The code in our bin file is quite simple.
import 'package:dart_coding_challenges_4_longest_word_1/dart_coding_challenges_4_longest_word_1.dart';
/*
This is solution 1 for our app! We are going to use Regular Expressions and
lists to make this simpler. If you want to see a more 'traditional' approach
we will do so in solution 2. That solution will not feature regular expresions or
use lists. Which approach do you prefer?
*/
void main(List<String> arguments) {
//Get our sanitized text from the command line.
Map<String, dynamic> data = getTextFromCommandLine();
//Traverse the list of words in the command line.
String longestWord = calculateLongestWord(data["wordList"]).toString();
//Print out our final statement.
print("The longest word in the text is \"$longestWord\" which contains ${longestWord.length} characters");
}
We are executing getTextFromCommandLine() to get input, creating a string called longestWord and running the calculateLongestWord() function using the list of words returned from our getTextFromCommandLine() function and then printing the results to the user.
/lib
Our lib folder contains the bulk of our application logic.
Obtaining Input
To obtain the input from the user, we will use the stdin.readLineSync()
function to capture text from the command line. We will then validate that the text meets the specified criteria using a do-while
loop. If the text is not valid, we will prompt the user for input again with a message indicating the problem.
Map<String, dynamic> getTextFromCommandLine() {
String inputText = "";
String cliMessage = "Please enter text with at least 1 sentence and 3 words:";
Map<String, dynamic> numData = {"wordCount": 0, "sentenceCount": 0};
do {
// Write message and execute readLineSync!
stdout.write("$cliMessage ");
inputText = stdin.readLineSync()!;
// Update word/sentences counter with current data:
numData = countSentencesAndWords(inputText);
// Return different message based on criteria in use!
if (inputText.isEmpty) {
cliMessage = "Please enter text with at least 1 sentence and 3 words:";
} else if (numData["wordCount"]! < 3) {
cliMessage = "Please enter 3 or more words!";
} else if (numData["sentenceCount"]! < 1) {
cliMessage = "Please enter at least one sentence: ";
}
} while (inputText.isEmpty || numData["wordCount"]! < 3 || numData["sentenceCount"]! < 1);
return {"inputText": inputText, "wordList": numData["words"]};
}
Validating Input & Getting List of Words
To validate that the input meets the specified criteria, as well as get the list of words, we will use the countSentencesAndWords()
function. This function will count the number of words and sentences in the input text and return a map with the data. Additionally, it will return a list of words. If the text is not valid, we will prompt the user for input again with a message indicating the problem.
Map<String, dynamic> countSentencesAndWords(String inputText) {
// Our Regular Expression to find sentences.
final RegExp sentenceRegExp = RegExp(r'(?<=[.!?])\s+');
// Our Regular Expression to find words without punctuation.
final RegExp wordRegExp = RegExp(r'\b\w+\b');
// Get the list of words without punctuation marks
List<String> words = wordRegExp.allMatches(inputText).map((match) => match.group(0) ?? '').toList();
return {
"wordCount": words.length,
"sentenceCount": inputText.split(sentenceRegExp).length,
"words": words,
};
}
We are taking our string, called inputText, and processing it through the user of regular expressions. Note that we are using a new method of the RegExp class called .allMatches.
allMatches, when used on our wordRegExp RegExp object, takes our inputText as an argument. It is then converted into a map. We are then taking any match of the Regular Expression, and using match.group(0) to take the entire match and record it. Then we are using the .toList() method to convert our data into a list. This list is being returned in the words “key” in the Map that is being sent back.
Now that we have our list of words, we need to find the longest word in it. We can achieve this by iterating through the list and keeping track of the current longest word we've found so far. If we find a word that's longer than the current longest word, we update the current longest word to be the new word.
Here's the function to find the longest word:
String calculateLongestWord(List<String> words) {
String currentLongestWord = "";
//Iterate through the list of words.
words.forEach((word) {
//If the word is longer than the previous record update it.
if (word.length >= currentLongestWord.length) {
currentLongestWord = word;
}
});
//Return the longest word!
return currentLongestWord;
}
The calculateLongestWord
function takes a list of words as input and returns the longest word in the list. We start by initializing the currentLongestWord
variable to be an empty string. We then iterate through each word in the list using the forEach
method from the List class.
For each word, we check if its length is greater than or equal to the length of the current longest word. If it is, we update the currentLongestWord
variable to be the new word. We continue this process until we've checked every word in the list.
Finally, we return the currentLongestWord
, which should be the longest word in the list.
Step 4: Putting it All Together
Now that we have all the pieces we need, we can put them together to create the final program as seen above in the /bin section. Here's what the main
function looks like:
void main(List<String> arguments) {
//Get our sanitized text from the command line.
Map<String, dynamic> data = getTextFromCommandLine();
//Traverse the list of words in the command line.
String longestWord = calculateLongestWord(data["wordList"]).toString();
//Print out our final statement.
print("The longest word in the text is \"$longestWord\" which contains ${longestWord.length} characters");}
We start by getting the user's input text using the getTextFromCommandLine
function we defined earlier. We then extract the list of words from the input data and pass it to the calculateLongestWord
function to find the longest word. Finally, we print out the longest word and its length using a formatted string.
And that's it! We've successfully solved the "Longest Word" coding challenge using Dart.
/test
Testing is a crucial part of software development, and it can help ensure that your code works as expected and avoid bugs in the future. In the tests folder of our Dart project, we have two test groups: countSentencesAndWords
and calculateLongestWord
.
Take a look at the code and then we will break it down:
import 'package:test/test.dart';
import 'package:dart_coding_challenges_4_longest_word_1/dart_coding_challenges_4_longest_word_1.dart';
void main() {
group('countSentencesAndWords', () {
test('Should correctly count words and sentences', () {
String inputText = 'Hello, World! This is a test. How are you?';
Map<String, dynamic> result = countSentencesAndWords(inputText);
expect(result['wordCount'], 9);
expect(result['sentenceCount'], 3);
expect(result['words'], ['Hello', 'World', 'This', 'is', 'a', 'test', 'How', 'are', 'you']);
});
test('Should handle empty input', () {
String inputText = '';
Map<String, dynamic> result = countSentencesAndWords(inputText);
expect(result['wordCount'], 0);
expect(result['sentenceCount'], 1);
expect(result['words'], []);
});
});
group('calculateLongestWord', () {
test('Should return the longest word', () {
List<String> words = ['Hello', 'World', 'This', 'is', 'a', 'test', 'How', 'are', 'you'];
String longestWord = calculateLongestWord(words);
expect(longestWord, 'World');
});
test('Should return an empty string for an empty list', () {
List<String> words = [];
String longestWord = calculateLongestWord(words);
expect(longestWord, '');
});
});
}
Notice that we are using some new functionality of the test library, group! This will allow you to group similar tests together. This time around the tests are more in-depth as well.
The first test group, countSentencesAndWords
, tests the function that takes a string input and returns a map with three pieces of information: the word count, the sentence count, and a list of the individual words in the input string. The first test in this group, Should correctly count words and sentences
, tests the function's ability to accurately count the words and sentences in a string that contains punctuation and multiple sentences. The test asserts that the function returns a map with a word count of 9, a sentence count of 3, and a list of the individual words in the input string.
The second test in this group, Should handle empty input
, tests the function's ability to handle empty input. The test asserts that the function returns a map with a word count of 0, a sentence count of 1, and an empty list of words.
The second test group, calculateLongestWord
, tests the function that takes a list of strings and returns the longest string in the list. The first test in this group, Should return the longest word
, tests the function's ability to accurately return the longest word in a list of strings. The test asserts that the function returns the string 'World' when given a list of words.
The second test in this group, Should return an empty string for an empty list
, tests the function's ability to handle empty input. The test asserts that the function returns an empty string when given an empty list.
By including tests for our functions, we can catch bugs and ensure that our code is working as expected. Additionally, testing helps document the expected behavior of our functions, making it easier for other developers to understand and use our code in the future. Beyond that, it helps us think clearly when designing the functions in our application.
Solution 2
This solution focuses on using a more “traditional” approach, meaning we are not taking advantage of regular expressions or other in-built dart functionality to make the problem easier. As with solution 1, we have three main files we are focusing on:
/bin
The bin file is identical to solution 1. No updates are required, but code is posted below for ease of viewing.
import 'package:dart_coding_challenges_4_longest_word_2/dart_coding_challenges_4_longest_word_2.dart';
/*
This is solution 1 for our app! We are going to use Regular Expressions and
lists to make this simpler. If you want to see a more 'traditional' approach
we will do so in solution 2. That solution will not feature regular expresions or
use lists. Which approach do you prefer?
*/
void main(List<String> arguments) {
//Get our sanitized text from the command line.
Map<String, dynamic> data = getTextFromCommandLine();
//Traverse the list of words in the command line.
String longestWord = calculateLongestWord(data["wordList"]).toString();
//Print out our final statement.
print("The longest word in the text is \"$longestWord\" which contains ${longestWord.length} characters");
}
/lib
The lib file is where we find the bulk of our changes. Full code listed here, and then we will break it down section by section.
import 'dart:io';
Map<String, dynamic> getTextFromCommandLine() {
String inputText = "";
String cliMessage = "Please enter text with at least 1 sentence and 3 words:";
Map<String, dynamic> numData = {"wordCount": 0, "sentenceCount": 0};
do {
//Write message and execute readLineSync!
stdout.write("$cliMessage ");
inputText = stdin.readLineSync()!;
//Update word/sentences counter with current data.
//I am defining this here so we don't have to repeatedly
//call the functioin in the while loop, etc.
numData = countSentencesAndWords(inputText);
//Return different message based on criteria in use!
if (inputText.isEmpty) {
cliMessage = "Please enter text with at least 1 sentence and 3 words:";
} else if (numData["wordCount"]! < 3) {
cliMessage = "Please enter 3 or more words!";
} else if (numData["sentenceCount"]! < 1) {
cliMessage = "Please enter at least one sentence: ";
}
} while (inputText.isEmpty || numData["wordCount"]! < 3 || numData["sentenceCount"]! < 1);
return {"inputText": inputText, "wordList": numData["words"]};
}
//Calculate the number of words and sentences in a string and then return a map
//with the data.
//This is a more classic implementation of solving the problem. We will explore a few other ways to do it, too!
Map<String, dynamic> countSentencesAndWords(String text) {
//The map we will eventually send back,
Map<String, dynamic> results = {"wordCount": 0, "sentenceCount": 0, "words": ""};
//The list of characters that can end a word or a sentence.
//We really only need to use a space to end words, because any , ; or : would be followed with a space anyway!
List<String> endWords = [" "];
List<String> endSentences = ["!", ".", "?"];
List<String> listOfWords = [];
//Local counters we can use to se tthe values.
int words = 0;
int sentences = 0;
String wordBuffer = "";
//A traditional way of solving the problem that does not use all the amazing dart functionality!
int numCharacters = text.length;
//Loop through every characater in the String to see how many word and sentence endings there are.
for (int i = 0; i < numCharacters; i++) {
//Add a word if the current character is found in our end words list.
//If this space is after the end of sentence punctuation we don't want to count it as a word!
if (!endWords.contains(text[i]) && !endSentences.contains(text[i])) {
wordBuffer += text[i];
}
if (endWords.contains(text[i]) && !endSentences.contains(text[i - 1])) {
listOfWords.add(wordBuffer);
wordBuffer = "";
words++;
}
//Add a sentence if the current character is in our end of character list.
//Prevent a sentence like "I am mad!!!" from generating 3 sentences.
if (endSentences.contains(text[i]) && !endSentences.contains(text[i - 1])) {
listOfWords.add(wordBuffer);
wordBuffer = "";
sentences++;
words++;
}
//If the user did not enter punctuation for the last character we must still classify it as a sentence!
if (i + 1 == numCharacters && !endSentences.contains(text[i])) {
listOfWords.add(wordBuffer);
wordBuffer = "";
sentences++;
words++;
}
}
results["wordCount"] = words;
results["sentenceCount"] = sentences;
results["words"] = listOfWords;
return results;
}
//For Solution 2! Simple function using the "reduce" function on a list.
String calculateLongestWord(List<String> words) {
// Use the reduce method to find the longest word.
/*
This effectively loops through the entire list and compares A with B, and then uses
a ternary operator to return the higher value.
*/
if (words.isEmpty) {
return '';
}
return words.reduce((a, b) => a.length > b.length ? a : b);
}
The getTextFromCommandLine
Function
This function is identical to solution 1.
The countSentencesAndWords
Function
//This is a more classic implementation of solving the problem. We will explore a few other ways to do it, too!
Map<String, dynamic> countSentencesAndWords(String text) {
//The map we will eventually send back,
Map<String, dynamic> results = {"wordCount": 0, "sentenceCount": 0, "words": ""};
//The list of characters that can end a word or a sentence.
//We really only need to use a space to end words, because any , ; or : would be followed with a space anyway!
List<String> endWords = [" "];
List<String> endSentences = ["!", ".", "?"];
List<String> listOfWords = [];
//Local counters we can use to se tthe values.
int words = 0;
int sentences = 0;
String wordBuffer = "";
//A traditional way of solving the problem that does not use all the amazing dart functionality!
int numCharacters = text.length;
//Loop through every characater in the String to see how many word and sentence endings there are.
for (int i = 0; i < numCharacters; i++) {
//Add a word if the current character is found in our end words list.
//If this space is after the end of sentence punctuation we don't want to count it as a word!
if (!endWords.contains(text[i]) && !endSentences.contains(text[i])) {
wordBuffer += text[i];
}
if (endWords.contains(text[i]) && !endSentences.contains(text[i - 1])) {
listOfWords.add(wordBuffer);
wordBuffer = "";
words++;
}
//Add a sentence if the current character is in our end of character list.
//Prevent a sentence like "I am mad!!!" from generating 3 sentences.
if (endSentences.contains(text[i]) && !endSentences.contains(text[i - 1])) {
listOfWords.add(wordBuffer);
wordBuffer = "";
sentences++;
words++;
}
//If the user did not enter punctuation for the last character we must still classify it as a sentence!
if (i + 1 == numCharacters && !endSentences.contains(text[i])) {
listOfWords.add(wordBuffer);
wordBuffer = "";
sentences++;
words++;
}
}
results["wordCount"] = words;
results["sentenceCount"] = sentences;
results["words"] = listOfWords;
return results;
}
The countSentencesAndWords
function is used to count the number of words and sentences in a string of text as well as creating a list of individual words. It does so by looping through the text character by character, keeping track of the number of words and sentences it encounters, and when the end of a word is detected, adding our wordBuffer variable into listOfWords.
We are adding each individual character into wordBuffer until the criteria in our if statements is met, which basically just indicate the end of a word or the end of a sentence. See the comments in the code for a breakdown regarding how these word.
The function takes a string of text as an argument and returns a Map
object with three keys: wordCount
, sentenceCount
, and words
. wordCount
and sentenceCount
are integers representing the number of words and sentences in the text, respectively. words
is a list of words derived from the text.
The calculateLongestWord
Function
//For Solution 2! Simple function using the "reduce" function on a list.
String calculateLongestWord(List<String> words) {
// Use the reduce method to find the longest word.
/*
This effectively loops through the entire list and compares A with B, and then uses
a ternary operator to return the higher value.
*/
if (words.isEmpty) {
return '';
}
return words.reduce((a, b) => a.length > b.length ? a : b);
}
The calculateLongestWord
function is used to find the longest word in a list of words. It does so using the reduce
method, which compares each element of the list to the current longest word and returns the longest word found.
Inside of the reduce method we are using a ternary operator.
A ternary operator is a shorthand conditional operator that allows you to write more concise code by evaluating an expression and returning a value based on whether it's true or false. In Dart, the ternary operator is represented by the "?" symbol and can be used in the following syntax: condition ? value_if_true : value_if_false
. For example, x > y ? x : y
means that if x is greater than y, the expression returns x, otherwise it returns y.
The test cases for solution 2 are similar to those for solution 1. They test the countSentencesAndWords
and calculateLongestWord
functions with different inputs to ensure they return the correct results.
/test
A series of tests have been written for this application.
import 'package:test/test.dart';
import '../lib/dart_coding_challenges_4_longest_word_2.dart';
void main() {
group('countSentencesAndWords', () {
test('Test with basic input', () {
String input = 'This is a test! Here is another sentence.';
var result = countSentencesAndWords(input);
expect(result['wordCount'], 8);
expect(result['sentenceCount'], 2);
expect(result['words'], [
'This',
'is',
'a',
'test',
'Here',
'is',
'another',
'sentence',
]);
});
test('Test with no end punctuation', () {
String input = 'This is a test Here is another sentence';
var result = countSentencesAndWords(input);
expect(result['wordCount'], 8);
expect(result['sentenceCount'], 1);
expect(result['words'], [
'This',
'is',
'a',
'test',
'Here',
'is',
'another',
'sentence',
]);
});
test('Test with multiple punctuations', () {
String input = 'This is a test!! Here is another sentence.';
var result = countSentencesAndWords(input);
expect(result['wordCount'], 8);
expect(result['sentenceCount'], 2);
expect(result['words'], [
'This',
'is',
'a',
'test',
'Here',
'is',
'another',
'sentence',
]);
});
});
group('calculateLongestWord', () {
test('Test with basic input', () {
List<String> input = ['This', 'is', 'a', 'test'];
var result = calculateLongestWord(input);
expect(result, 'test');
});
test('Test with empty list', () {
List<String> input = [];
var result = calculateLongestWord(input);
expect(result, '');
});
test('Test with all words having same length', () {
List<String> input = ['one', 'two', 'cat'];
var result = calculateLongestWord(input);
expect(result, 'cat');
});
});
}
The test cases for solution 2 are similar to those for solution 1. They test the countSentencesAndWords
and calculateLongestWord
functions with different inputs to ensure they return the correct results.
Conclusion
Solution 2 for "Dart Coding Challenge #4" is a classic implementation of the longest word problem that doesn't use any advanced Dart features. It's a good example of how to solve problems using basic programming techniques.
If you're interested in practicing more coding challenges like this one, please be sure to see our “Dart Coding Challenges” playlist on our YouTube Channel.