diff --git a/README.md b/README.md index f822b62..36a9070 100644 --- a/README.md +++ b/README.md @@ -111,6 +111,7 @@ const count = stringzy.analyze.wordCount('Hello world'); // 2 - [patternCount](#patterncount) - calculates the number of times a specific pattern occurs in a given text - [vowelConsonantCount](#vowelconsonantcount) - Counts the number of vowels and consonants in a given string - [checkMultiplePatterns](#checkmultiplepatterns) - Finds occurrences of multiple patterns within a given text using Rabin–Karp algorithm (case sensitive) +- [checkSubsequence](#checksubsequence) - Checks whether the second string is a subsequence of the first string (case sensitive) ### Formatting @@ -993,6 +994,37 @@ checkMultiplePatterns('hello world', ['xyz', '123']); | text | string | required | The text to search within. | | patterns | string\[ ] | required | An array of patterns to search for (each must be a string). | +#### checkSubsequence(str, sub) + +Checks whether a given string sub is a subsequence of another string str. +A subsequence maintains the relative order of characters, but they do not need to be consecutive. +Case-sensitive comparison is performed. +Spaces and all characters are treated literally. + +```javascript +isSubsequence('abcde', 'ace'); +// true → 'a', 'c', 'e' appear in order + +isSubsequence('abracadabra', 'aaa'); +// true → multiple 'a's in correct order + +isSubsequence('abcde', 'aec'); +// false → order is broken (e comes before c) + +isSubsequence('anything', ''); +// true → empty subsequence is always valid + +isSubsequence('AbC', 'AC'); +// true → exact case matches + +isSubsequence('a b c', 'abc'); +// false → spaces count as characters +``` +| Parameter | Type | Default | Description | +| --------- | ------ | -------- | ----------------------------------------------- | +| str | string | required | The main string to check within. | +| sub | string | required | The subsequence string to verify against `str`. | + --- diff --git a/src/analyzing/checkSubsequence.ts b/src/analyzing/checkSubsequence.ts new file mode 100644 index 0000000..c702fe8 --- /dev/null +++ b/src/analyzing/checkSubsequence.ts @@ -0,0 +1,33 @@ +/** + * Checks whether the second string is a subsequence of the first string. + * + * A subsequence means all characters of the second string appear in the first string + * in the same relative order, but not necessarily consecutively. + * + * Is case sensitive + * + * @param {string} str1 - The string to check against. + * @param {string} str2 - The candidate subsequence. + * @returns {boolean} True if str2 is a subsequence of str1, otherwise false. + * @throws {TypeError} If either input is not a string. + */ +export function checkSubsequence(str1: string, str2: string): boolean { + if (typeof str1 !== "string" || typeof str2 !== "string") { + throw new TypeError("Both inputs must be strings"); + } + + // empty subsequence is always valid + if (str2 === "") return true; + + let i = 0; // pointer for str2 (the subsequence) + let j = 0; // pointer for str1 (the main string) + + while (i < str2.length && j < str1.length) { + if (str2[i] === str1[j]) { + i++; + } + j++; + } + + return i === str2.length; +} \ No newline at end of file diff --git a/src/analyzing/index.ts b/src/analyzing/index.ts index 4ed49d5..141ed05 100644 --- a/src/analyzing/index.ts +++ b/src/analyzing/index.ts @@ -7,6 +7,7 @@ export { stringSimilarity } from './stringSimilarity'; export { patternCount } from './patternCount'; export { vowelConsonantCount } from './vowelConsonantCount'; export { checkMultiplePatterns } from './checkMultiplePatterns'; +export { checkSubsequence } from './checkSubsequence'; export { functionWordCount } from './functionWordCount'; export { contentWordCount } from './contentWordCount'; @@ -21,6 +22,7 @@ import { stringSimilarity } from './stringSimilarity'; import { patternCount } from './patternCount'; import { vowelConsonantCount } from './vowelConsonantCount'; import { checkMultiplePatterns } from './checkMultiplePatterns'; +import { checkSubsequence } from './checkSubsequence'; @@ -33,5 +35,6 @@ export const analyzing = { stringSimilarity, patternCount, vowelConsonantCount, - checkMultiplePatterns + checkMultiplePatterns, + checkSubsequence }; diff --git a/src/tests/analyzing/checkSubsequence.test.ts b/src/tests/analyzing/checkSubsequence.test.ts new file mode 100644 index 0000000..001cae4 --- /dev/null +++ b/src/tests/analyzing/checkSubsequence.test.ts @@ -0,0 +1,35 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert'; +import { checkSubsequence } from '../../analyzing/checkSubsequence'; + +describe('checkSubsequence', () => { + it('returns true for valid subsequences', () => { + assert.strictEqual(checkSubsequence('abcde', 'ace'), true); + assert.strictEqual(checkSubsequence('abracadabra', 'aaa'), true); + assert.strictEqual(checkSubsequence('hello world', 'hlo'), true); + }); + + it('returns false when order is broken', () => { + assert.strictEqual(checkSubsequence('abcde', 'aec'), false); + assert.strictEqual(checkSubsequence('abcdef', 'z'), false); + }); + + it('handles empty subsequence', () => { + assert.strictEqual(checkSubsequence('anything', ''), true); + }); + + it('is case-sensitive', () => { + assert.strictEqual(checkSubsequence('abc', 'A'), false); + assert.strictEqual(checkSubsequence('AbC', 'AC'), true); + }); + + it('handles spaces as normal characters', () => { + assert.strictEqual(checkSubsequence('a b c', 'abc'), true); + assert.strictEqual(checkSubsequence('a b c', 'a c'), true); + }); + + it('throws if inputs are not strings', () => { + assert.throws(() => checkSubsequence(123 as any, 'abc'), /Both inputs must be strings/); + assert.throws(() => checkSubsequence('abc', null as any), /Both inputs must be strings/); + }); +});