Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -539,6 +540,36 @@ stringPermutations('a1!');
| --------- | ------ | -------- | ----------------------------------------------------- |
| str | string | required | The input string to generate all unique permutations. |

#### <a id="stringcombinations"></a>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

Expand Down
44 changes: 44 additions & 0 deletions src/tests/transformations/stringCombinations.test.ts
Original file line number Diff line number Diff line change
@@ -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/);
});
});
5 changes: 4 additions & 1 deletion src/transformations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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,
Expand All @@ -56,5 +58,6 @@ export const transformations = {
deburr,
numberToText,
reverseWordsInString,
stringPermutations
stringPermutations,
stringCombinations
};
44 changes: 44 additions & 0 deletions src/transformations/stringCombinations.ts
Original file line number Diff line number Diff line change
@@ -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<string>();

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