Замена последнего вхождения в строке

JavaScriptJavaScriptBeginner
Практиковаться сейчас

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

💡 Этот учебник переведен с английского с помощью ИИ. Чтобы просмотреть оригинал, вы можете перейти на английский оригинал

Введение

В этом практическом занятии (лабораторной работе) мы рассмотрим манипуляции со строками в JavaScript, реализовав функцию replaceLast. В отличие от встроенного метода replace, который заменяет первое вхождение шаблона, наша функция будет специфически ориентирована на замену последнего вхождения. Эта возможность полезна во многих реальных сценариях, таких как обновление расширений файлов, модификация URL-адресов или очистка текста.

В рамках этого практического занятия вы узнаете о методах работы со строками, регулярных выражениях и о том, как их комбинировать для создания полезной вспомогательной функции. К концу этого практического занятия вы получите тщательное понимание техник манипуляции строками в JavaScript и reusable function (повторно используемую функцию) для своих проектов по программированию.

Понимание проблемы и настройка окружения

Прежде чем мы начнем писать код, давайте разберемся, что должна делать наша функция replaceLast:

  1. Принимать три параметра:

    • str: Входная строка, которую нужно изменить
    • pattern: Подстрока или регулярное выражение, которое нужно найти
    • replacement: Строка, на которую нужно заменить последнее вхождение
  2. Возвращать новую строку с замененным последним вхождением шаблона.

Давайте создадим файл JavaScript для реализации нашей функции:

  1. Перейдите в директорию проекта в проводнике файлов WebIDE.
  2. Создайте новый файл с именем replaceLast.js в директории replace-last.
  3. Добавьте в файл следующую базовую структуру:
// 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

Чтобы убедиться, что все настроено правильно, давайте добавим простой тест:

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

Теперь давайте запустим наш код, чтобы увидеть текущий вывод:

  1. Откройте Терминал в WebIDE.
  2. Перейдите в директорию replace-last:
    cd ~/project/replace-last
  3. Запустите файл JavaScript с помощью Node.js:
    node replaceLast.js

В выводе вы должны увидеть Hello world world, потому что наша функция в настоящее время просто возвращает исходную строку без каких-либо изменений.

Реализация основной логики функции

Теперь, когда мы понимаем проблему, давайте реализуем основную функциональность нашей функции replaceLast. Сначала мы сосредоточимся на обработке строковых шаблонов, а затем в следующем шаге рассмотрим регулярные выражения.

Когда шаблон представляет собой строку, мы можем использовать метод lastIndexOf для нахождения позиции последнего вхождения. Как только мы знаем эту позицию, мы можем использовать метод slice для перестроения строки с вставленным заменяющим значением.

Обновите свою функцию replaceLast следующей реализацией:

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;
}

Обновите свои тестовые случаи, чтобы проверить, правильно ли функция обрабатывает строковые шаблоны:

// 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)

Запустите код еще раз, чтобы увидеть обновленный вывод:

node replaceLast.js

Теперь вы должны увидеть, что последнее вхождение строкового шаблона заменено в каждом тестовом случае. Например, "Hello world JavaScript" вместо "Hello world world".

Обработка регулярных выражений

Теперь давайте усовершенствуем нашу функцию для обработки шаблонов регулярных выражений. Когда шаблон представляет собой регулярное выражение, нам нужно:

  1. Найти все совпадения в строке.
  2. Получить последнее совпадение.
  3. Заменить это последнее совпадение на строку замены.

Обновите функцию replaceLast для обработки шаблонов регулярных выражений:

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;
}

Добавьте тестовые случаи для шаблонов регулярных выражений:

// 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)

Запустите код еще раз, чтобы увидеть обновленный вывод:

node replaceLast.js

Теперь и строковые шаблоны, и шаблоны регулярных выражений должны корректно работать в функции replaceLast.

Оптимизация функции и тестирование крайних случаев

Наша функция работает для базовых случаев, но давайте оптимизируем ее и обработаем некоторые крайние случаи:

  1. Мы должны проверить, является ли входная строка пустой.
  2. Мы можем упростить обработку регулярных выражений.
  3. Мы должны обработать случаи, когда замена не является строкой.

Обновите функцию replaceLast с учетом этих оптимизаций:

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;
}

Давайте добавим больше тестовых случаев, чтобы охватить крайние случаи:

// 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)

Запустите код еще раз, чтобы увидеть обновленный вывод:

node replaceLast.js

Эта версия функции обрабатывает больше крайних случаев и сохраняет чистоту кода. Теперь у вас есть надежная функция replaceLast, готовая к использованию в ваших проектах.

Создание модуля и использование функции

В этом последнем шаге мы преобразуем нашу функцию в полноценный JavaScript-модуль, который можно импортировать и использовать в других файлах. Это распространенная практика в реальном JavaScript-разработке.

Сначала создадим файл модуля для нашей функции. Создайте новый файл с именем replaceLastModule.js в директории replace-last:

/**
 * 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;

Теперь создадим другой файл для использования нашего модуля. Создайте новый файл с именем app.js в директории replace-last:

// 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>")
);

Запустите приложение, чтобы увидеть, как работает модуль:

node app.js

Вы должны увидеть вывод со всеми примерами, демонстрирующими, как функция replaceLast может быть использована в различных сценариях.

Поздравляем! Вы успешно создали полезную JavaScript-утилиту и упаковали ее в модуль, который можно повторно использовать в своих проектах.

Резюме

В этом практическом занятии (лабораторной работе) вы узнали, как создать JavaScript-утилиту, которая заменяет последнее вхождение шаблона в строке. В процессе вы получили ценные знания о следующем:

  • Методах манипуляции строками, таких как lastIndexOf и slice
  • Работе с регулярными выражениями в JavaScript
  • Обработке различных типов входных данных и крайних случаев
  • Создании и экспорте JavaScript-модулей
  • Написании чистого, повторно используемого кода с правильной документацией

Функция replaceLast, которую вы создали, представляет собой практическую утилиту, которая может быть использована во многих реальных сценариях, таких как манипуляция путями к файлам, модификация URL-адресов и обработка текста. Этот тип манипуляции строк часто необходим, но напрямую не доступен в стандартной библиотеке JavaScript.

Понимая концепции и техники, рассмотренные в этом практическом занятии, вы теперь лучше подготовлены для решения аналогичных задач по манипуляции строками в своих будущих программировании проектах.