diff --git a/README.md b/README.md index 0ecc224..976d094 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ const count = stringzy.analyze.wordCount('Hello world'); // 2 - [numberToText](#numbertotext) - Converts a number to its text representation in specified language - [reverseWordsInString](#reversewordsinstring) - Reverses the order of words in a given string - [stringPermutations](#stringpermutations) - Generates all unique permutations of a given string. +- [stringCombinations](#stringcombinations) - Generates all unique combinations of a given string. ### Validations @@ -539,6 +540,36 @@ stringPermutations('a1!'); | --------- | ------ | -------- | ----------------------------------------------------- | | str | string | required | The input string to generate all unique permutations. | +#### stringCombinations(str) + +Generates all unique combinations (subsequences) of a given string, including the empty string. +Duplicate characters are handled by ensuring only unique combinations are returned. +The order of combinations in the output array is not guaranteed. + +```javascript +stringCombinations('ab'); +// ["", "a", "b", "ab"] + +stringCombinations('abc'); +// ["", "a", "b", "c", "ab", "ac", "bc", "abc"] + +stringCombinations('aab'); +// ["", "a", "b", "aa", "ab", "aab"] + +stringCombinations(''); +// [""] + +stringCombinations('A'); +// ["", "A"] + +stringCombinations('!@'); +// ["", "!", "@", "!@"] +``` + +| Parameter | Type | Default | Description | +| --------- | ------ | -------- | ------------------------------------------------------ | +| str | string | required | The input string to generate unique combinations from. | + ### ✅ Validations diff --git a/src/tests/transformations/stringCombinations.test.ts b/src/tests/transformations/stringCombinations.test.ts new file mode 100644 index 0000000..0d4449f --- /dev/null +++ b/src/tests/transformations/stringCombinations.test.ts @@ -0,0 +1,44 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert'; +import { stringCombinations } from '../../transformations/stringCombinations'; + +describe('stringCombinations', () => { + it('returns correct combinations for 2 characters', () => { + const expected = ['', 'a', 'b', 'ab']; + assert.deepStrictEqual(stringCombinations('ab').sort(), expected.sort()); + }); + + it('returns correct combinations for 3 unique characters', () => { + const expected = ['', 'a', 'b', 'c', 'ab', 'ac', 'bc', 'abc']; + assert.deepStrictEqual(stringCombinations('abc').sort(), expected.sort()); + }); + + it('handles repeated characters correctly', () => { + const expected = ['', 'a', 'b', 'aa', 'ab', 'aab']; + assert.deepStrictEqual(stringCombinations('aab').sort(), expected.sort()); + }); + + it('returns only empty string for empty input', () => { + assert.deepStrictEqual(stringCombinations(''), ['']); + }); + + it('handles single character input', () => { + assert.deepStrictEqual(stringCombinations('a'), ['', 'a']); + }); + + it('is case-sensitive', () => { + const expected = ['', 'A', 'b', 'Ab']; + assert.deepStrictEqual(stringCombinations('Ab').sort(), expected.sort()); + }); + + it('handles special characters correctly', () => { + const expected = ['', '!', '@', '!@']; + assert.deepStrictEqual(stringCombinations('!@').sort(), expected.sort()); + }); + + it('throws an error if input is not a string', () => { + assert.throws(() => stringCombinations(123 as any), /Input must be a string/); + assert.throws(() => stringCombinations(null as any), /Input must be a string/); + assert.throws(() => stringCombinations(undefined as any), /Input must be a string/); + }); +}); diff --git a/src/transformations/index.ts b/src/transformations/index.ts index b7b322b..311c538 100644 --- a/src/transformations/index.ts +++ b/src/transformations/index.ts @@ -16,6 +16,7 @@ export { maskSegment } from './maskSegment'; export { numberToText } from './numberToText/main'; export { reverseWordsInString } from './reverseWordsInString '; export { stringPermutations } from './stringPermutations'; +export { stringCombinations } from './stringCombinations'; import { camelCase } from './camelCase'; import { capitalizeWords } from './capitalizeWords'; @@ -36,6 +37,7 @@ import { deburr } from './deburr'; import { numberToText } from './numberToText/main'; import { reverseWordsInString } from './reverseWordsInString '; import { stringPermutations } from './stringPermutations'; +import { stringCombinations } from './stringCombinations'; export const transformations = { camelCase, @@ -56,5 +58,6 @@ export const transformations = { deburr, numberToText, reverseWordsInString, - stringPermutations + stringPermutations, + stringCombinations }; diff --git a/src/transformations/stringCombinations.ts b/src/transformations/stringCombinations.ts new file mode 100644 index 0000000..2a0564b --- /dev/null +++ b/src/transformations/stringCombinations.ts @@ -0,0 +1,44 @@ +/** + * Generates all unique combinations (subsequences) of a given string, + * including the empty string. + * + * Handles duplicate characters by ensuring only unique combinations are returned. + * The order of combinations in the output array is not guaranteed. + * + * @param {string} str - The input string to generate combinations from. + * @returns {string[]} An array containing all unique combinations of the string. + * @throws {TypeError} If the input is not a string. + * + * @example + * stringCombinations("ab"); + * // ["", "a", "b", "ab"] + * + * @example + * stringCombinations("abc"); + * // ["", "a", "b", "c", "ab", "ac", "bc", "abc"] + * + * @example + * stringCombinations("aab"); + * // ["", "a", "b", "aa", "ab", "aab"] + * + * @example + * stringCombinations(""); + * // [""] + */ +export function stringCombinations(str: string): string[] { + if (typeof str !== 'string') { + throw new TypeError('Input must be a string'); + } + + const results = new Set(); + + function backtrack(start: number, path: string) { + results.add(path); + for (let i = start; i < str.length; i++) { + backtrack(i + 1, path + str[i]); + } + } + + backtrack(0, ''); + return Array.from(results); +}