Mastering String Manipulation in JavaScript: 12 Solved Exercises
Fernando Nunes
Software Engineer | Full Stack Developer | Angular | Nodejs | Nestjs | React | AWS | Azure
String manipulation is a core skill for any developer. In this article, we will explore 12 essential string manipulation techniques in JavaScript, followed by advanced challenges that will test and enhance your skills. These challenges are inspired by hard-level problems from LeetCode and HackerRank, ensuring you are well-prepared for the most difficult string manipulation scenarios.
1. Concatenating Strings
How to Think:
Concatenating strings is fundamental when you need to build new strings from smaller parts. In complex challenges, this might involve constructing answers based on various conditions.
Exercise:
Given an array of strings, concatenate them in all possible orders, returning the lexicographically smallest order.
const minLexicographicalOrder = (strings) => {
// We sort the strings based on the lexicographically smallest combination
return strings.sort((a, b) => (a + b).localeCompare(b + a)).join('');
};
// Testing the function with an array of strings
const result = minLexicographicalOrder(["c", "cb", "cba"]); // "cbacbc"
console.log(result);
Detailed Explanation:
const minLexicographicalOrder = (strings) => {
return strings.sort((a, b) => (a + b).localeCompare(b + a)).join('');
.join('');
const result = minLexicographicalOrder(["c", "cb", "cba"]);
console.log(result);
2. Extracting Substrings with substring() and slice()
How to Think:
Extracting substrings is crucial in problems where you need to analyze specific parts of a string. In advanced challenges, you might need to generate and test many substrings to find a specific pattern, like palindromes.
Exercise:
Given a string, generate all possible combinations of substrings, check if any of them is a palindrome, and return the largest palindrome found.
const longestPalindromeSubstring = (s) => {
// Initialize a variable to store the largest palindrome found so far
let longest = '';
// The first loop iterates through each character of the string 's' as the starting point of the substring
for (let i = 0; i < s.length; i++) {
// The second loop iterates from the current position 'i' to the end of the string, forming substrings
for (let j = i + 1; j <= s.length; j++) {
// We extract a substring from 'i' to 'j'
const substr = s.slice(i, j);
// We check if the current substring is a palindrome
if (substr === substr.split('').reverse().join('')) {
// If the length of the current palindrome is greater than the longest palindrome found so far,
// we update 'longest' with the new palindrome
if (substr.length > longest.length) {
longest = substr;
}
}
}
}
// Return the largest palindrome found
return longest;
};
// Testing the function with a string
const result = longestPalindromeSubstring("babad"); // "bab" or "aba"
console.log(result);
Detailed Explanation:
let longest = '';
for (let i = 0; i < s.length; i++) {
for (let j = i + 1; j <= s.length; j++) {
const substr = s.slice(i, j);
if (substr === substr.split('').reverse().join('')) {
if (substr.length > longest.length) {
longest = substr;
}
return longest;
const result = longestPalindromeSubstring("babad");
console.log(result); // "bab" or "aba"
3. Finding the Position of a Substring with indexOf()
How to Think:
Using indexOf() is necessary when you need to locate parts of a string for subsequent decisions. In complex challenges, this can be combined with loops and conditions to process large strings.
Exercise:
Find all occurrences of a substring within a larger string and return the starting positions of each occurrence.
const findAllOccurrences = (text, pattern) => {
// Initialize an array to store the positions of occurrences
let positions = [];
// We use indexOf to find the first occurrence of the pattern
let pos = text.indexOf(pattern);
// As long as the found position is not -1, we continue the search
while (pos !== -1) {
// Add the current position to the array of positions
positions.push(pos);
// Find the next occurrence of the pattern, starting right after the current position
pos = text.indexOf(pattern, pos + 1);
}
// Return the array with all the positions of the occurrences
return positions;
};
// Testing the function with a string and a pattern
const result = findAllOccurrences("ababcabc", "abc");
console.log(result); // [2, 5]
Detailed Explanation:
let positions = [];
let pos = text.indexOf(pattern);
while (pos !== -1) {
positions.push(pos);
pos = text.indexOf(pattern, pos + 1);
multiple times.
return positions;
const result = findAllOccurrences("ababcabc", "abc");
console.log(result); // [2, 5]
4. Replacing Substrings with replace()
How to Think:
Replacing substrings is useful in problems where specific parts of a string need to be modified. In more advanced challenges, this might involve complex conditions and multiple replacements.
Exercise:
Given a string, replace all occurrences of a word with another, but only if the word is not surrounded by alphabetic characters.
const conditionalReplace = (text, target, replacement) => {
// Construct a pattern using the target word surrounded by word boundaries
const pattern = new RegExp(`\\b${target}\\b`, 'g');
// Replace occurrences of the pattern with the replacement word
return text.replace(pattern, replacement);
};
// Testing the function with a string
const result = conditionalReplace("the theater is theirs", "the", "a");
console.log(result); // "a theater is theirs"
Detailed Explanation:
const pattern = new RegExp(`\\b${target}\\b`, 'g');
return text.replace(pattern, replacement);
const result = conditionalReplace("the theater is theirs", "the", "a");
console.log(result); // "a theater is theirs"
5. Trimming Whitespace with trim()
How to Think:
Trimming whitespace is crucial for cleaning up data, especially when processing user input. In challenges, this can be combined with other string manipulation techniques.
Exercise:
Given a string containing words separated by varying amounts of spaces, normalize the string by removing extra spaces between the words and any leading or trailing spaces.
const normalizeSpaces = (text) => {
// First, trim leading and trailing spaces with trim()
// Then, split the string into words using split with a regex that captures one or more spaces
// Finally, join the words with a single space between them using join
return text.trim().split(/\s+/).join(' ');
};
// Testing the function with a string
const result = normalizeSpaces(" I love programming "); // "I love programming"
console.log(result);
Detailed Explanation:
text.trim()
.split(/\s+/)
.join(' ');
const result = normalizeSpaces(" I love programming ");
console.log(result); // "I love programming"
6. Splitting Strings with split()
How to Think:
split() is essential in problems where you need to separate a string into components for further processing. Complex challenges might require intelligent use of delimiters.
Exercise:
Given a string that contains multiple words separated by non-alphabetic characters, split the string into individual words.
const splitWords = (text) => {
// Use split with a regex that matches any non-alphabetic character
return text.split(/[^a-zA-Z]+/);
};
// Testing the function with a string
const result = splitWords("I,love-programming!");
console.log(result); // ["I", "love", "programming"]
Detailed Explanation:
return text.split(/[^a-zA-Z]+/);
const result = splitWords("I,love-programming!");
console.log(result); // ["I", "love", "programming"]
7. Changing Case with toUpperCase() and toLowerCase()
How to Think:
Changing the case of strings can be useful in challenges where data normalization is needed, especially for comparisons.
Exercise:
Convert a string to camelCase, where the first word is lowercase and the following words have the first letter capitalized.
const toCamelCase = (text) => {
// First, split the string into words based on spaces
// Then, use map to transform the first word to lowercase and the following words to camelCase
// Finally, join the words without spaces to form the camelCase string
return text.split(' ').map((word, index) =>
index === 0 ? word.toLowerCase() : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
).join('');
};
// Testing the function with a string
const result = toCamelCase("i love programming");
console.log(result); // "iLoveProgramming"
Detailed Explanation:
return text.split(' ')
.map((word, index) =>
index === 0 ? word.toLowerCase() : word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
)
.join('');
const result = toCamelCase("i love programming");
console.log(result); // "iLoveProgramming"
8. Checking Substring Presence with includes()
How to Think:
includes() is useful for quick checks within strings, especially in problems that require the presence or absence of patterns.
Exercise:
Check if a string contains all the words in a list of words, returning true if all are present.
const containsAllWords = (text, words) => {
// Use every to check if all words in the list are included in the text string
return words.every(word => text.includes(word));
};
// Testing the function with a string and a list of words
const result = containsAllWords("I love programming", ["love", "programming"]);
console.log(result); // true
Detailed Explanation:
return words.every(word => text.includes(word));
const result = containsAllWords("I love programming", ["love", "programming"]);
console.log(result); // true
9. Repeating Strings with repeat()
How to Think:
Repeating strings can be useful in problems that require repetitive patterns, such as formatting or creating padding strings.
Exercise:
Given a short string, create a string that repeats the content until it reaches a specific length.
const repeatToLength = (text, length) => {
// Use repeat to repeat the string enough times to exceed the desired length
// Then use slice to cut the repeated string to the exact desired length
return text.repeat(Math.ceil(length / text.length)).slice(0, length);
};
// Testing the function with a short string and a desired length
const result = repeatToLength("abc", 10);
console.log(result); // "abcabcabca"
Detailed Explanation:
text.repeat(Math.ceil(length / text.length))
.slice(0, length);
const result = repeatToLength("abc", 10);
console.log(result); // "abcabcabca"
10. Comparing Strings with localeCompare()
How to Think:
Comparing strings is essential in custom sorting, especially when the language or culture affects the order.
Exercise:
Sort a list of city names based on reverse alphabetical order using locale rules.
const sortCitiesDescending = (cities) => {
// Use sort with a custom comparison function that reverses the order using localeCompare
return cities.sort((a, b) => b.localeCompare(a));
};
// Testing the function with an array of city names
const result = sortCitiesDescending(["Tokyo", "Paris", "New York", "Berlin"]); // ["Tokyo", "Paris", "New York", "Berlin"]
console.log(result);
Detailed Explanation:
return cities.sort((a, b) => b.localeCompare(a));
const result = sortCitiesDescending([ "Berlin", "Paris", "New York", "Tokyo" ]);
console.log(result); // ["Tokyo", "Paris", "New York", "Berlin"]
11. Checking for Prefixes and Suffixes with startsWith() and endsWith()
How to Think:
Use startsWith() and endsWith() to check if a string begins or ends with a specific substring. This is useful for validating formats, such as URLs or file paths.
Exercise:
Given an array of URLs, check how many start with "https" and how many end with ".com".
const checkUrls = (urls) => {
let httpsCount = 0;
let comCount = 0;
urls.forEach(url => {
if (url.startsWith("https")) {
httpsCount++;
}
if (url.endsWith(".com")) {
comCount++;
}
});
return { httpsCount, comCount };
};
// Testing the function with an array of URLs
const result = checkUrls(["https://example.com", "https://example.org", "https://mysite.com", "ftp://another.com"]);
console.log(result); // { httpsCount: 2, comCount: 3 }
Detailed Explanation:
let httpsCount = 0;
let comCount = 0;
urls.forEach(url => {
if (url.startsWith("https")) {
httpsCount++;
}
if (url.endsWith(".com")) {
comCount++;
}
return { httpsCount, comCount };
const result = checkUrls(["https://example.com", "https://example.org", "https://mysite.com", "ftp://another.com"]);
console.log(result); // { httpsCount: 2, comCount: 3 }
12. Transforming Arrays into Strings with join()
How to Think:
When you have an array of strings that needs to be joined into a single string, join() is the ideal function. It can be used to create sentences, CSVs, or other forms of output.
Exercise:
Given an array of words, convert it into a single sentence where each word is capitalized and separated by a space.
const createSentence = (words) => {
// Use map to capitalize the first letter of each word
// Then use join to combine the words into a single sentence
return words.map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ');
};
// Testing the function with an array of words
const result = createSentence(["the", "quick", "brown", "fox"]); Fox"
console.log(result); // "The Quick Brown
Detailed Explanation:
return words.map(word => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
const result = createSentence(["the", "quick", "brown", "fox"]);
console.log(result); // "The Quick Brown Fox"
References:
Cloud Software Engineer | Fullstack Software Engineer | AWS | PHP | Laravel | ReactJs | Docker
7 个月Useful tips
Senior iOS Engineer | Mobile Developer | Swift | Objective-C
7 个月Good tips!
Fullstack Software Engineer | Node.js | React.js | Javascript & Typescript | Go Developer
7 个月These string manipulation exercises in JavaScript are incredibly useful! Thanks for sharing this practical guide!
Senior Ux Designer | Product Designer | UX/UI Designer | UI/UX Designer | Figma | Design System |
7 个月Don't underestimate the importance of testing and debugging when working with strings. A single incorrect character or misplaced space can cause errors that are difficult to track down. Use tools like console.log() and debugger to test your code and ensure that it's working as expected.
Senior Flutter Developer | iOS Developer | Mobile Developer | Flutter | Swift | UIKit | SwiftUI
7 个月Thanks for sharing