DEV Community

Ignacio Villamar
Ignacio Villamar

Posted on

String Manipulation Techniques

In JavaScript we frequently come across scenarios where we need to perform operations on strings. These could range from simple tasks like finding out how long a string is, to more challenging ones like removing duplicate characters.

A practical way to tackle many of the string manipulation tasks involves converting the string into an array of characters. This pattern is highly effective as it allows us to leverage array methods such as split, filter, map, reduce, and join, among others. These methods can be chained together, leading to concise and often straightforward solutions.

The pattern involves the following steps:

  1. Split the string into an array of characters.
  2. Perform an operation on this array.
  3. Join the array back into a string if necessary.

Let's dive into some practical examples to demonstrate this pattern in action.


Remove Duplicate String Characters

The removeDuplicateCharacters function eliminates duplicate letters from a string.

function removeDuplicateCharacters(str) {
    return [...new Set(str.split(''))].join('');
}
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation:

  1. Input: The function takes one argument, str, which is expected to be a string.
  2. Processing: The function does the following:
    • str.split(''): This splits the string into an array of its individual characters.
    • new Set(...): This creates a new Set from the array. A Set is a built-in JavaScript object that only allows unique values. So, if there are any duplicate characters in the array, they will be removed when the array is converted to a Set.
    • [...new Set(...)]: This uses the spread operator ... to convert the Set back into an array. The array now contains the characters of the original string, but any duplicates have been removed.
    • .join(''): This joins the elements of the array back together into a single string.
  3. Output: The function returns the resulting string, which contains the same characters as the input string but with any duplicates removed.

Count String Characters

The countCharacters function counts each character's occurrence in a string.

function countCharacters(str = '') {
    if (str.length === 0) return {};

    return str
        .toLowerCase()
        .split('')
        .reduce((acc, curr) => {
            acc[curr] = acc[curr] ? acc[curr] + 1 : 1;
            return acc;
        }, {});
}
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation:

  1. Input: The function takes one argument, str, which is expected to be a string. The string is defaulted to an empty string in case no string is provided and immediately returns an empty object and the rest of the function is not executed.
  2. Processing: The function does the following:
    • str.toLowerCase(): This converts the string to lowercase. This ensures that the function counts uppercase and lowercase letters as the same character.
    • str.split(''): This splits the string into an array of its individual characters.
    • The reduce method to iterate over the array of characters. The reduce method takes two arguments: a callback function and an initial value. The callback function also takes two arguments: an accumulator acc and the current value curr.
      • acc[curr] = acc[curr] ? acc[curr] + 1 : 1;: This checks if the current character curr is already a property in the accumulator object acc. If it is, it increments the value of that property by 1. If it's not, it sets the value of that property to 1. This effectively counts the number of occurrences of each character.
  3. Output: The function returns an object where the keys are the characters from the input string and the values are the counts of the occurrences of those characters.

Get Unique Intersecting Characters

The getUniqueIntersectingCharacters function returns duplicate letters from two strings.

function getUniqueIntersectingCharacters(str = '', str2 = '') {
    if (str.length === 0 || str2.length === 0) {
        return '';
    }

    return str
        .split('')
        .filter(char => str2.includes(char))
        .join('');
}
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation:

  1. Input: The function takes two arguments, str and str2, which are expected to be strings. If either string is empty, the function immediately returns an empty string and the rest of the function is not executed.
  2. Processing: The function does the following:
    • str.split(''): This splits the first string into an array of its individual characters.
    • .filter(char => str2.includes(char)): This filters the array of characters to only include those that are also found in the second string.
    • .join(''): This joins the filtered characters back together into a single string.
  3. Output: The function returns a string containing the unique duplicate letters found in both input strings.

Check if a String is Palindrome

The isPalindrome function checks if a given string is a palindrome. A palindrome is a string that reads the same forwards and backwards, ignoring spaces, punctuation, and capitalization.

function isPalindrome(str): boolean {
    const lowerCaseStr = str.toLowerCase();
    return lowerCaseStr === lowerCaseStr.split('').reverse().join('');
}
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation:

  1. Input: The function takes one argument, str, which is expected to be a string.
  2. Processing: The function does the following:
    • const lowerCaseStr = str.toLowerCase();: This line converts the input string to lowercase and assigns the result to the lowerCaseStr variable. This ensures the palindrome check is case-insensitive.
    • lowerCaseStr.split(''): This line splits lowerCaseStr into an array of its individual characters.
    • The reverse method is called on the array, which reverses the order of its elements.
    • The join('') method is called on the reversed array to join its elements back together into a single string.
    • lowerCaseStr === lowerCaseStr.split('').reverse().join(''): This line checks if lowerCaseStr is equal to the reversed string. If they are equal, it means that str is a palindrome.
  3. Output: The function returns a boolean value. If str is a palindrome, the function returns true. If str is not a palindrome, the function returns false.

Check if the Given Strings are Anagrams

An anagram is a word or phrase created by rearranging the letters of another word or phrase, usually using each letter only once.

Let's first examine a simple, straightforward solution, and then explore an optimized version to illustrate some of the principles mentioned in the previous examples.

function isAnagram(str1, str2): boolean {
    const normalize = str => {
        return str.replace(/\s/g, '').toLowerCase().split('').sort().join('');
    };

    return normalize(str1) === normalize(str2);
}
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation:

  1. Input: The function takes two string arguments, str1 and str2 .
  2. Processing: Inside the isAnagram function, a helper function normalize is defined. This function takes a string as input (str) and returns a "normalized" version of it. The normalization process involves several steps:
    • str.replace(/\s/g, ''):  This removes all spaces from the string. The regular expression \s matches any whitespace character, and the g flag makes it replace all occurrences, not just the first one.
    • .toLowerCase(): This converts all characters in the string to lowercase. This ensures that the anagram check is case-insensitive.
    • .split(''): This splits the string into an array of individual characters.
    • .sort(): This sorts the array of characters in alphabetical order.
    • .join(''): This joins the sorted characters back into a string.
    • The normalize function is then called on both str1 and str2, and the results are compared for equality.
  3. Output: The function returns true if the normalized versions of str1 and str2 are equal (i.e., they are anagrams), and false otherwise.

Although the isAnagram function is simple and straightforward, it's not well-suited for large strings. This is because the sort operation is computationally expensive, usually having a time complexity of O(n log n), where n represents the string's character count. Furthermore, the join operation, which involves string concatenation, can also be costly regarding time and space complexity, particularly for large strings.

Let's explore an optimized solution that's not just efficient, but also very versatile for all sorts of string manipulation algorithms.

Optimized Anagram Solution

An optimized solution of the isAnagram function could use a character map to count the occurrences of each character in the strings, which would avoid the need for sorting and reduce the number of operations on the strings. Let's dive into the code and unpack it, step by step!

function isAnagramOptimized(str1, str2) {
    const buildCharMap = str => {
        const cleanStr = str.replace(/\s/g, '').toLowerCase();
        return cleanStr.split('').reduce((acc, curr) => {
            acc[curr] = acc[curr] ? acc[curr] + 1 : 1;
            return acc;
        }, {});
    };

    const str1CharMap = buildCharMap(str1);
    const str2CharMap = buildCharMap(str2);

    if (Object.keys(str1CharMap).length !== Object.keys(str2CharMap).length) {
        return false;
    }

    for (let char in str1CharMap) {
        if (str1CharMap[char] !== str2CharMap[char]) {
            return false;
        }
    }

    return true;
}
Enter fullscreen mode Exit fullscreen mode

Step-by-step explanation:

  1. Input: The function takes two string arguments, str1 and str2.
  2. Processing: It makes use of a helper function buildCharMap, which follows the similar normalization process as described in isAnagram but further builds a character map.
    • cleanStr.split(''): This line splits the cleanStr string into an array of individual characters. For example, if cleanStr is "abc", it becomes ["a", "b", "c"].
    • The reduce method to transform the array of characters into a single output value. In this case, the output value is an object, where each key is a character from the string and each value is the count of that character in the string.
      • acc is the accumulator, which is the output value that's being built up over the course of the reduce operation. It starts as an empty object {} and eventually becomes the character count object.
      • curr is the current character being processed in the string.
    • acc[curr] = acc[curr] ? acc[curr] + 1 : 1;: This line updates the count of the current character in the accumulator object. If the character already exists in the object acc[curr] is truthy, it increments the count by 1. If the character doesn't exist in the object acc[curr] is falsy, it sets the count to 1.
    • return acc: This line returns the updated accumulator object, which becomes the input to the next call of the reduce callback function for the next character in the array. After all characters have been processed, the reduce method returns the final accumulator object, which is the character count object.
  3. It then checks if the two character maps have the same number of keys . If they do not, the function returns false and ends.
  4. If the character maps do have the same number of keys, the function iterates over the keys of the first character map. For each key, it checks if the value of that key in the first character map is equal to the value of that key in the second character map. If they are not equal, it means that the character occurs a different number of times in str1 and str2, so the function returns false and ends.
  5. Output: If the function has not returned false after checking all keys, it means that str1 and str2 are anagrams of each other, so the function returns true.

Conclusion

We delved into practical examples of string manipulation in JavaScript. We saw how a similar pattern of converting a string into an array of characters can resolve different issues. Each task demonstrated the utility of array methods when applied to strings. The resulting solutions were efficient and straightforward. This pattern is incredibly useful in solving complex problems, so remember to keep it in your toolbox. Happy Coding!

Top comments (0)