From 73a9f34a6fe58040808bfca6e47869f6f76aed2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=EC=9A=A9=ED=9B=88?= <78959175+hoonloper@users.noreply.github.com> Date: Mon, 27 May 2024 20:50:28 +0900 Subject: [PATCH] =?UTF-8?q?fix(utils):=20=ED=8A=B9=EC=88=98=20=EB=AC=B8?= =?UTF-8?q?=EC=9E=90=EC=97=90=20=EB=8C=80=ED=95=9C=20=EA=B0=9C=EC=88=98?= =?UTF-8?q?=EC=99=80=20=EA=B3=B5=EB=B0=B1=EC=97=90=20=EB=8C=80=ED=95=9C=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EC=B6=94=EA=B0=80=20(#168)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(utils): 특수 문자에 대한 개수와 공백에 대한 처리 추가 * chore: console.log 제거 * refactor(utils): 함수 배치 수정 및 while 문 가독성 향상 * docs(utils): 중복 문자열에 대한 옵션 허용 문서화 --- .../utils/string/countSubstringOccurrences.md | 4 ++ .../countSubstringOccurrences.spec.ts | 16 ++++++++ .../string/countSubstringOccurrences/index.ts | 39 +++++++++++++++++-- 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/docs/docs/utils/string/countSubstringOccurrences.md b/docs/docs/utils/string/countSubstringOccurrences.md index 7fa9fbc16..266ea04b4 100644 --- a/docs/docs/utils/string/countSubstringOccurrences.md +++ b/docs/docs/utils/string/countSubstringOccurrences.md @@ -19,4 +19,8 @@ import { countSubstringOccurrences } from '@modern-kit/utils'; const str = 'apple banana apple grapes apple'; const count1 = countSubstringOccurrences(str, 'apple'); // 3 const count2 = countSubstringOccurrences(str, 'apple banana'); // 1 + +const duplicatedStr = 'aaaa' +const count3 = countSubstringOccurrences(duplicatedStr, 'aa'); // 2, double counting not allowed +const count4 = countSubstringOccurrences(duplicatedStr, 'aa', { overlap: true }); // 3, double counting allowed ``` \ No newline at end of file diff --git a/packages/utils/src/string/countSubstringOccurrences/countSubstringOccurrences.spec.ts b/packages/utils/src/string/countSubstringOccurrences/countSubstringOccurrences.spec.ts index 761ee9df4..f94a85641 100644 --- a/packages/utils/src/string/countSubstringOccurrences/countSubstringOccurrences.spec.ts +++ b/packages/utils/src/string/countSubstringOccurrences/countSubstringOccurrences.spec.ts @@ -16,4 +16,20 @@ describe('countSubstringOccurrences', () => { expect(countSubstringOccurrences(str2, '테스트')).toBe(4); expect(countSubstringOccurrences(str2, '테스트용 문자열')).toBe(2); }); + + it('should return 0 when either source or target is an empty string', () => { + expect(countSubstringOccurrences('', 'abc')).toBe(0); + expect(countSubstringOccurrences('abc', '')).toBe(0); + }); + + it('should return the correct count of occurrences', () => { + expect(countSubstringOccurrences('abc', 'abc')).toBe(1); + expect(countSubstringOccurrences('aaaa', 'aa', { overlap: true })).toBe(3); + expect(countSubstringOccurrences('banana', 'na')).toBe(2); + }); + + it('should handle special characters in the target string', () => { + expect(countSubstringOccurrences('a.b.c', '.')).toBe(2); + expect(countSubstringOccurrences('a(b)c(d)e', '(')).toBe(2); + }); }); diff --git a/packages/utils/src/string/countSubstringOccurrences/index.ts b/packages/utils/src/string/countSubstringOccurrences/index.ts index a5cb53ec8..f98ed2791 100644 --- a/packages/utils/src/string/countSubstringOccurrences/index.ts +++ b/packages/utils/src/string/countSubstringOccurrences/index.ts @@ -1,6 +1,39 @@ -export const countSubstringOccurrences = (source: string, target: string) => { - const regex = new RegExp(target, 'g'); - const matches = source.match(regex); +type Options = { overlap: boolean }; + +const escapeRegExp = (str: string): string => { + return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +}; + +const countAllowOverlap = (source: string, regex: RegExp) => { + let count = 0; + let match = regex.exec(source); + while (match !== null) { + count++; + regex.lastIndex = match.index + 1; + + match = regex.exec(source); + } + + return count; +}; + +const countExceptOverlap = (source: string, regex: RegExp) => { + const matches = source.match(regex); return matches ? matches.length : 0; }; + +export const countSubstringOccurrences = ( + source: string, + target: string, + options: Options = { overlap: false } +): number => { + if (target === '') return 0; + + const escapedTarget = escapeRegExp(target); + const regex = new RegExp(escapedTarget, 'g'); + + return options.overlap + ? countAllowOverlap(source, regex) + : countExceptOverlap(source, regex); +};