Replace Last Occurrence in String

JavaScriptJavaScriptBeginner
Practice Now

This tutorial is from open-source community. Access the source code

Introduction

In this lab, we will explore string manipulation in JavaScript by implementing a replaceLast function. Unlike the built-in replace method which replaces the first occurrence of a pattern, our function will specifically target the last occurrence. This capability is useful in many real-world scenarios such as updating file extensions, modifying URLs, or cleaning up text.

Throughout this lab, you will learn about string methods, regular expressions, and how to combine them to create a useful utility function. By the end of this lab, you will have a solid understanding of JavaScript string manipulation techniques and a reusable function for your coding projects.

Understanding the Problem and Setting Up

Before we start coding, let us understand what our replaceLast function should do:

  1. Accept three parameters:

    • str: The input string to modify
    • pattern: The substring or regular expression to search for
    • replacement: The string to replace the last occurrence with
  2. Return a new string with the last occurrence of the pattern replaced.

Let us create a JavaScript file to implement our function:

  1. Navigate to the project directory in the WebIDE file explorer.
  2. Create a new file named replaceLast.js in the replace-last directory.
  3. Add the following basic structure to the file:
// Function to replace the last occurrence of a pattern in a string
function replaceLast(str, pattern, replacement) {
  // Our implementation will go here
  return str;
}

// We will add test cases here later

To check that everything is set up correctly, let us add a simple test:

// Example usage
console.log(replaceLast("Hello world world", "world", "JavaScript"));

Now, let us run our code to see the current output:

  1. Open the Terminal in WebIDE
  2. Navigate to the replace-last directory:
    cd ~/project/replace-last
  3. Run the JavaScript file using Node.js:
    node replaceLast.js

You should see Hello world world in the output because our function currently just returns the original string without making any changes.

Implementing the Core Function Logic

Now that we understand the problem, let us implement the core functionality of our replaceLast function. We will focus on handling string patterns first, then tackle regular expressions in the next step.

When the pattern is a string, we can use the lastIndexOf method to find the position of the last occurrence. Once we know this position, we can use the slice method to rebuild the string with the replacement inserted.

Update your replaceLast function with the following implementation:

function replaceLast(str, pattern, replacement) {
  // Ensure inputs are valid
  if (typeof str !== "string") {
    return str;
  }

  if (typeof pattern === "string") {
    // Find the position of the last occurrence
    const lastIndex = str.lastIndexOf(pattern);

    // If pattern not found, return original string
    if (lastIndex === -1) {
      return str;
    }

    // Rebuild the string with the replacement
    const before = str.slice(0, lastIndex);
    const after = str.slice(lastIndex + pattern.length);
    return before + replacement + after;
  }

  // We'll handle regex patterns in the next step
  return str;
}

Update your test cases to check that the function correctly handles string patterns:

// Test cases for string patterns
console.log(replaceLast("Hello world world", "world", "JavaScript")); // Should output: "Hello world JavaScript"
console.log(replaceLast("abcabcabc", "abc", "123")); // Should output: "abcabc123"
console.log(replaceLast("abcdef", "xyz", "123")); // Should output: "abcdef" (pattern not found)

Run the code again to see the updated output:

node replaceLast.js

You should now see the last occurrence of the string pattern replaced in each test case. For example, "Hello world JavaScript" instead of "Hello world world".

Handling Regular Expression Patterns

Now let us enhance our function to handle regular expression patterns. When the pattern is a regular expression, we need to:

  1. Find all matches in the string
  2. Get the last match
  3. Replace that last match with the replacement string

Update your replaceLast function to handle regular expression patterns:

function replaceLast(str, pattern, replacement) {
  // Ensure inputs are valid
  if (typeof str !== "string") {
    return str;
  }

  // Handle string patterns
  if (typeof pattern === "string") {
    const lastIndex = str.lastIndexOf(pattern);
    if (lastIndex === -1) {
      return str;
    }
    const before = str.slice(0, lastIndex);
    const after = str.slice(lastIndex + pattern.length);
    return before + replacement + after;
  }

  // Handle regular expression patterns
  if (pattern instanceof RegExp) {
    // Create a new RegExp with global flag to find all matches
    const globalRegex = new RegExp(pattern.source, "g");

    // Find all matches
    const matches = str.match(globalRegex);

    // If no matches, return original string
    if (!matches || matches.length === 0) {
      return str;
    }

    // Get the last match
    const lastMatch = matches[matches.length - 1];

    // Find the position of the last match
    const lastIndex = str.lastIndexOf(lastMatch);

    // Rebuild the string with the replacement
    const before = str.slice(0, lastIndex);
    const after = str.slice(lastIndex + lastMatch.length);
    return before + replacement + after;
  }

  // If pattern is neither string nor RegExp, return original string
  return str;
}

Add test cases for regular expression patterns:

// Test cases for string patterns
console.log(replaceLast("Hello world world", "world", "JavaScript")); // Should output: "Hello world JavaScript"
console.log(replaceLast("abcabcabc", "abc", "123")); // Should output: "abcabc123"
console.log(replaceLast("abcdef", "xyz", "123")); // Should output: "abcdef" (pattern not found)

// Test cases for regular expression patterns
console.log(replaceLast("Hello world world", /world/, "JavaScript")); // Should output: "Hello world JavaScript"
console.log(replaceLast("123 456 789", /\d+/, "numbers")); // Should output: "123 456 numbers"
console.log(replaceLast("abcdef", /xyz/, "123")); // Should output: "abcdef" (pattern not found)

Run the code again to see the updated output:

node replaceLast.js

Both string patterns and regular expression patterns should now work correctly in the replaceLast function.

Optimizing the Function and Testing Edge Cases

Our function works for basic cases, but let us optimize it and handle some edge cases:

  1. We should check if the input string is empty
  2. We can simplify our regex handling
  3. We should handle cases where the replacement is not a string

Update your replaceLast function with these optimizations:

function replaceLast(str, pattern, replacement) {
  // Ensure str is a string
  if (typeof str !== "string") {
    return str;
  }

  // If str is empty or pattern is not provided, return original string
  if (str === "" || pattern === undefined) {
    return str;
  }

  // Ensure replacement is a string
  if (replacement === undefined) {
    replacement = "";
  } else if (typeof replacement !== "string") {
    replacement = String(replacement);
  }

  // Handle string patterns
  if (typeof pattern === "string") {
    const lastIndex = str.lastIndexOf(pattern);
    if (lastIndex === -1) {
      return str;
    }
    return (
      str.slice(0, lastIndex) +
      replacement +
      str.slice(lastIndex + pattern.length)
    );
  }

  // Handle regular expression patterns
  if (pattern instanceof RegExp) {
    // Create a global version of the regex to find all matches
    const globalRegex = new RegExp(
      pattern.source,
      "g" + (pattern.ignoreCase ? "i" : "") + (pattern.multiline ? "m" : "")
    );

    // Find all matches
    const matches = str.match(globalRegex);

    // If no matches, return original string
    if (!matches || matches.length === 0) {
      return str;
    }

    // Get the last match
    const lastMatch = matches[matches.length - 1];

    // Find the position of the last match
    const lastIndex = str.lastIndexOf(lastMatch);

    // Rebuild the string with the replacement
    return (
      str.slice(0, lastIndex) +
      replacement +
      str.slice(lastIndex + lastMatch.length)
    );
  }

  // If pattern is neither string nor RegExp, return original string
  return str;
}

Let us add more test cases to cover the edge cases:

// Test cases for string patterns
console.log(replaceLast("Hello world world", "world", "JavaScript")); // Should output: "Hello world JavaScript"
console.log(replaceLast("abcabcabc", "abc", "123")); // Should output: "abcabc123"
console.log(replaceLast("abcdef", "xyz", "123")); // Should output: "abcdef" (pattern not found)

// Test cases for regular expression patterns
console.log(replaceLast("Hello world world", /world/, "JavaScript")); // Should output: "Hello world JavaScript"
console.log(replaceLast("123 456 789", /\d+/, "numbers")); // Should output: "123 456 numbers"
console.log(replaceLast("abcdef", /xyz/, "123")); // Should output: "abcdef" (pattern not found)

// Edge cases
console.log(replaceLast("", "abc", "123")); // Should output: "" (empty string)
console.log(replaceLast("abcdef", "", "123")); // Should output: "abcde123f" (empty pattern)
console.log(replaceLast("abcdef", "def", "")); // Should output: "abc" (empty replacement)
console.log(replaceLast("AbCdEf", /[a-z]/, "X")); // Should output: "AbCdEX" (case-sensitive regex)
console.log(replaceLast("AbCdEf", /[a-z]/i, "X")); // Should output: "AbCdEX" (case-insensitive regex)

Run the code again to see the updated output:

node replaceLast.js

This version of the function handles more edge cases and maintains clean code. You now have a robust replaceLast function ready to use in your projects.

Creating a Module and Using the Function

In this final step, we will convert our function into a proper JavaScript module that can be imported and used in other files. This is a common practice in real-world JavaScript development.

First, let us create a module file for our function. Create a new file called replaceLastModule.js in the replace-last directory:

/**
 * Replaces the last occurrence of a pattern in a string.
 *
 * @param {string} str - The input string.
 * @param {string|RegExp} pattern - The pattern to replace (string or RegExp).
 * @param {string} replacement - The replacement string.
 * @returns {string} - The string with the last occurrence replaced.
 */
function replaceLast(str, pattern, replacement) {
  // Ensure str is a string
  if (typeof str !== "string") {
    return str;
  }

  // If str is empty or pattern is not provided, return original string
  if (str === "" || pattern === undefined) {
    return str;
  }

  // Ensure replacement is a string
  if (replacement === undefined) {
    replacement = "";
  } else if (typeof replacement !== "string") {
    replacement = String(replacement);
  }

  // Handle string patterns
  if (typeof pattern === "string") {
    const lastIndex = str.lastIndexOf(pattern);
    if (lastIndex === -1) {
      return str;
    }
    return (
      str.slice(0, lastIndex) +
      replacement +
      str.slice(lastIndex + pattern.length)
    );
  }

  // Handle regular expression patterns
  if (pattern instanceof RegExp) {
    // Create a global version of the regex to find all matches
    const globalRegex = new RegExp(
      pattern.source,
      "g" + (pattern.ignoreCase ? "i" : "") + (pattern.multiline ? "m" : "")
    );

    // Find all matches
    const matches = str.match(globalRegex);

    // If no matches, return original string
    if (!matches || matches.length === 0) {
      return str;
    }

    // Get the last match
    const lastMatch = matches[matches.length - 1];

    // Find the position of the last match
    const lastIndex = str.lastIndexOf(lastMatch);

    // Rebuild the string with the replacement
    return (
      str.slice(0, lastIndex) +
      replacement +
      str.slice(lastIndex + lastMatch.length)
    );
  }

  // If pattern is neither string nor RegExp, return original string
  return str;
}

// Export the function
module.exports = replaceLast;

Now, let us create another file to use our module. Create a new file called app.js in the replace-last directory:

// Import the replaceLast function
const replaceLast = require("./replaceLastModule");

// Examples of using the replaceLast function
console.log(
  "Example 1:",
  replaceLast("Hello world world", "world", "JavaScript")
);
console.log("Example 2:", replaceLast("abcabcabc", "abc", "123"));
console.log("Example 3:", replaceLast("file.txt.backup.txt", ".txt", ".md"));
console.log("Example 4:", replaceLast("123 456 789", /\d+/, "numbers"));
console.log(
  "Example 5:",
  replaceLast("The fox jumped over the lazy dog", /[a-z]+/i, "cat")
);

// Practical examples
const filePath = "/path/to/my/file.txt";
console.log("File with new extension:", replaceLast(filePath, ".txt", ".md"));

const url = "https://example.com/products/category/item?color=red";
console.log("URL with updated parameter:", replaceLast(url, "red", "blue"));

const htmlTag = "<div class='container'><p>Text</p></div>";
console.log(
  "HTML with replaced tag:",
  replaceLast(htmlTag, /<\/?\w+>/g, "<span>")
);

Run the app to see how the module works:

node app.js

You should see the output with all the examples demonstrating how the replaceLast function can be used in various scenarios.

Congratulations. You have successfully created a useful JavaScript utility function and packaged it as a module that can be reused across your projects.

Summary

In this lab, you have learned how to create a JavaScript utility function that replaces the last occurrence of a pattern in a string. Throughout the process, you have gained valuable knowledge about:

  • String manipulation methods like lastIndexOf and slice
  • Working with regular expressions in JavaScript
  • Handling different types of inputs and edge cases
  • Creating and exporting JavaScript modules
  • Writing clean, reusable code with proper documentation

The replaceLast function you have built is a practical utility that can be used in many real-world scenarios such as file path manipulation, URL modifications, and text processing. This type of string manipulation is commonly needed but not directly available in JavaScript's standard library.

By understanding the concepts and techniques covered in this lab, you are now better equipped to solve similar string manipulation problems in your future programming projects.