문자열에서 마지막 일치 항목 바꾸기

Beginner

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

소개

이 랩에서는 replaceLast 함수를 구현하여 JavaScript 에서 문자열 조작을 탐구합니다. 패턴의 첫 번째 발생을 대체하는 내장 replace 메서드와 달리, 저희 함수는 마지막 발생을 특별히 대상으로 합니다. 이 기능은 파일 확장자 업데이트, URL 수정 또는 텍스트 정리와 같은 많은 실제 시나리오에서 유용합니다.

이 랩을 통해 문자열 메서드, 정규 표현식, 그리고 이를 결합하여 유용한 유틸리티 함수를 만드는 방법을 배우게 됩니다. 이 랩이 끝나면 JavaScript 문자열 조작 기술과 코딩 프로젝트에 재사용 가능한 함수에 대한 확실한 이해를 갖게 될 것입니다.

문제 이해 및 설정

코딩을 시작하기 전에, replaceLast 함수가 수행해야 할 작업을 이해해 봅시다:

  1. 세 개의 매개변수를 받습니다:

    • str: 수정할 입력 문자열
    • pattern: 검색할 부분 문자열 또는 정규 표현식 (regular expression)
    • replacement: 마지막 발생을 대체할 문자열
  2. 패턴의 마지막 발생이 대체된 새로운 문자열을 반환합니다.

함수를 구현하기 위해 JavaScript 파일을 만들어 봅시다:

  1. WebIDE 파일 탐색기에서 프로젝트 디렉토리로 이동합니다.
  2. replace-last 디렉토리에 replaceLast.js라는 새 파일을 만듭니다.
  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. Node.js 를 사용하여 JavaScript 파일을 실행합니다:
    node replaceLast.js

현재 함수가 변경 사항 없이 원래 문자열을 반환하기 때문에 출력에 Hello world world가 표시됩니다.

핵심 함수 로직 구현

이제 문제를 이해했으니, replaceLast 함수의 핵심 기능을 구현해 봅시다. 먼저 문자열 패턴을 처리하는 데 집중하고, 다음 단계에서 정규 표현식 (regular expression) 을 다루겠습니다.

패턴이 문자열인 경우, 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 world" 대신 "Hello world JavaScript"가 출력됩니다.

정규 표현식 (Regular Expression) 패턴 처리

이제 정규 표현식 패턴을 처리하도록 함수를 개선해 봅시다. 패턴이 정규 표현식인 경우 다음을 수행해야 합니다:

  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 함수에서 올바르게 작동해야 합니다.

함수 최적화 및 엣지 케이스 (Edge Case) 테스트

저희 함수는 기본적인 경우에 작동하지만, 최적화하고 몇 가지 엣지 케이스를 처리해 보겠습니다:

  1. 입력 문자열이 비어 있는지 확인해야 합니다.
  2. 정규 표현식 (regex) 처리를 단순화할 수 있습니다.
  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 개발에서 흔히 사용되는 방식입니다.

먼저, 함수에 대한 모듈 파일을 만들어 보겠습니다. replace-last 디렉토리에 replaceLastModule.js라는 새 파일을 만듭니다:

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

이제 모듈을 사용하기 위해 다른 파일을 만들어 보겠습니다. replace-last 디렉토리에 app.js라는 새 파일을 만듭니다:

// 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 유틸리티 함수를 만드는 방법을 배웠습니다. 이 과정을 통해 다음과 같은 귀중한 지식을 얻었습니다.

  • lastIndexOfslice와 같은 문자열 조작 메서드
  • JavaScript 에서 정규 표현식 (regular expression) 사용
  • 다양한 유형의 입력 및 엣지 케이스 (edge case) 처리
  • JavaScript 모듈 생성 및 내보내기
  • 적절한 문서화와 함께 깔끔하고 재사용 가능한 코드 작성

구축한 replaceLast 함수는 파일 경로 조작, URL 수정 및 텍스트 처리와 같은 많은 실제 시나리오에서 사용할 수 있는 실용적인 유틸리티입니다. 이러한 유형의 문자열 조작은 일반적으로 필요하지만 JavaScript 의 표준 라이브러리에서는 직접 사용할 수 없습니다.

이 랩에서 다룬 개념과 기술을 이해함으로써, 앞으로 프로그래밍 프로젝트에서 유사한 문자열 조작 문제를 해결하는 데 더 능숙해졌습니다.