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
59 changes: 43 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,15 @@ const isValid = stringzy.validate.isEmail('user@example.com'); // true
const count = stringzy.analyze.wordCount('Hello world'); // 2
```

## ✨ What’s new (perf update)

This release contains a performance-focused update to the permutations utilities:

- `stringPermutations(input: string)` β€” rewritten to use an optimized iterative approach to reduce recursion overhead and peak memory usage for longer strings.
- `stringPermutationsGenerator(input: string)` β€” a new generator-based API that yields permutations lazily so you can iterate large permutation sets without allocating the full result array in memory.

These changes improve throughput and reduce memory pressure when working with larger inputs. Note: complexity remains O(n!) β€” this update optimizes allocation and recursion overhead. If your code relied on a precise ordering from a previous implementation, run your test-suite as ordering may differ in edge cases.

## πŸ“‹ Table of Contents

### Transformations
Expand All @@ -80,8 +89,9 @@ const count = stringzy.analyze.wordCount('Hello world'); // 2
- [splitChunks](#splitchunks) - Breaks a string down into chunks of specified length.
- [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.
- [stringPermutations](#stringpermutations) - Generates all unique permutations of a given string.
- [stringPermutationsGenerator](#stringpermutationsgenerator) - Generator-based permutations API for lazy iteration.
- [stringCombinations](#stringcombinations) - Generates all unique combinations of a given string.

### Validations

Expand Down Expand Up @@ -519,13 +529,16 @@ reverseWordsInString('single-word');
| --------- | ------ | -------- | --------------------------- |
| str | string | required | The input string to reverse |

#### <a id="stringpermutations"></a>`stringPermutations(str)`
#### <a id="stringpermutations"></a>`stringPermutations(input: string): string[]`

Generates all unique permutations of the given string. Repeated characters are handled by ensuring only unique permutations are included in the returned array.

Generates all unique permutations of a given string.
Repeated characters are handled by ensuring only unique permutations are included in the output array.
The order of permutations is not guaranteed.
- Uses an optimized iterative algorithm under the hood to reduce recursion depth and intermediate allocations (lower peak memory usage and faster runtime for many practical inputs).
- Note: complexity remains O(n!) β€” this is an optimization of allocation/recursion overhead, not the factorial growth.

```javascript
import { stringPermutations } from 'stringzy';

stringPermutations('ab');
// ['ab', 'ba']

Expand All @@ -534,20 +547,34 @@ stringPermutations('abc');

stringPermutations('aab');
// ['aab', 'aba', 'baa']
```

| Parameter | Type | Default | Description |
| --------- | ------ | -------- | ----------------------------------------------------- |
| input | string | required | The input string to generate all unique permutations. |

stringPermutations('');
// ['']
#### <a id="stringpermutationsgenerator"></a>`stringPermutationsGenerator(input: string): Generator<string, void, unknown>`

stringPermutations('a');
// ['a']
Generator-based API that yields permutations one-by-one. Use this when you only need to process permutations sequentially or when the full result set would not fit in memory.

stringPermutations('a1!');
// ['a1!', 'a!1', '1a!', '1!a', '!a1', '!1a']
- Lazily produces permutations; does not allocate the entire permutation set in memory.
- Particularly useful for memory-sensitive workflows or streaming processing.

```javascript
import { stringPermutationsGenerator } from 'stringzy';

for (const p of stringPermutationsGenerator('abcd')) {
console.log(p);
// process each permutation without building a giant array in memory
}

// If you must collect them all:
const perms = Array.from(stringPermutationsGenerator('abcd'));
```

| Parameter | Type | Default | Description |
| --------- | ------ | -------- | ----------------------------------------------------- |
| str | string | required | The input string to generate all unique permutations. |
| Parameter | Type | Default | Description |
| --------- | ------ | -------- | ------------------------------------------------------ |
| input | string | required | The input string to generate permutations from. |

#### <a id="stringcombinations"></a>`stringCombinations(str)`

Expand Down Expand Up @@ -869,7 +896,7 @@ isMacAddress("aa:bb:cc:dd:ee:ff"); // true
isMacAddress("FF-FF-FF-FF-FF-FF"); // true

isMacAddress("00:1G:2B:3C:4D:5E"); // false (invalid hex digit)
isMacAddress("00:1A-2B:3C-4D:5E"); // false (mixed separators)
isMacAddress("00:1A-2B:3C:4D:5E"); // false (mixed separators)
isMacAddress("001A:2B:3C:4D:5E"); // false (wrong group length)
isMacAddress("hello-world-mac"); // false (invalid format)
isMacAddress(""); // false (empty string)
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

88 changes: 87 additions & 1 deletion src/tests/transformations/stringPermutations.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, it } from 'node:test';
import assert from 'node:assert';
import { stringPermutations } from '../../transformations/stringPermutations';
import { stringPermutations, stringPermutationsGenerator } from '../../transformations/stringPermutations';

describe('stringPermutations', () => {
it('returns correct permutations for small strings', () => {
Expand Down Expand Up @@ -55,4 +55,90 @@ describe('stringPermutations', () => {
assert.throws(() => stringPermutations(null as any), /Input must be a string/);
assert.throws(() => stringPermutations(undefined as any), /Input must be a string/);
});

it('respects limit parameter', () => {
const result = stringPermutations('abcdef', 3);
assert.strictEqual(result.length, 3);

// All results should be valid permutations
for (const perm of result) {
assert.strictEqual(perm.length, 6);
assert.strictEqual([...perm].sort().join(''), 'abcdef');
}
});

it('throws error for negative limit', () => {
assert.throws(() => stringPermutations('abc', -1), /Limit must be non-negative/);
});

it('handles limit of 0', () => {
const result = stringPermutations('abc', 0);
assert.strictEqual(result.length, 0);
});

it('handles limit larger than total permutations', () => {
const result = stringPermutations('abc', 100);
assert.strictEqual(result.length, 6); // 3! = 6
});
});

describe('stringPermutationsGenerator', () => {
it('yields correct permutations for small strings', () => {
const result = Array.from(stringPermutationsGenerator('ab')).sort();
assert.deepStrictEqual(result, ['ab', 'ba']);
});

it('yields all permutations for 3 unique characters', () => {
const result = Array.from(stringPermutationsGenerator('abc')).sort();
const expected = ['abc', 'acb', 'bac', 'bca', 'cab', 'cba'];
assert.deepStrictEqual(result, expected);
});

it('handles repeated characters correctly', () => {
const result = Array.from(stringPermutationsGenerator('aab')).sort();
const expected = ['aab', 'aba', 'baa'];
assert.deepStrictEqual(result, expected);
});

it('handles single character input', () => {
const result = Array.from(stringPermutationsGenerator('a'));
assert.deepStrictEqual(result, ['a']);
});

it('handles empty string input', () => {
const result = Array.from(stringPermutationsGenerator(''));
assert.deepStrictEqual(result, ['']);
});

it('is case-sensitive', () => {
const result = Array.from(stringPermutationsGenerator('Ab')).sort();
const expected = ['Ab', 'bA'];
assert.deepStrictEqual(result, expected);
});

it('handles special characters', () => {
const result = Array.from(stringPermutationsGenerator('!@')).sort();
const expected = ['!@', '@!'];
assert.deepStrictEqual(result, expected);
});

it('throws an error if input is not a string', () => {
// Generator functions throw when first consumed, not when created
const gen1 = stringPermutationsGenerator(123 as any);
assert.throws(() => gen1.next(), /Input must be a string/);

const gen2 = stringPermutationsGenerator(null as any);
assert.throws(() => gen2.next(), /Input must be a string/);

const gen3 = stringPermutationsGenerator(undefined as any);
assert.throws(() => gen3.next(), /Input must be a string/);
});

it('can be used with for...of loop', () => {
const results: string[] = [];
for (const perm of stringPermutationsGenerator('abc')) {
results.push(perm);
}
assert.strictEqual(results.length, 6);
});
});
5 changes: 3 additions & 2 deletions src/transformations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export { escapeHtml } from './escapeHTML';
export { maskSegment } from './maskSegment';
export { numberToText } from './numberToText/main';
export { reverseWordsInString } from './reverseWordsInString ';
export { stringPermutations } from './stringPermutations';
export { stringPermutations, stringPermutationsGenerator } from './stringPermutations';
export { stringCombinations } from './stringCombinations';

import { camelCase } from './camelCase';
Expand All @@ -36,7 +36,7 @@ import { maskSegment } from './maskSegment';
import { deburr } from './deburr';
import { numberToText } from './numberToText/main';
import { reverseWordsInString } from './reverseWordsInString ';
import { stringPermutations } from './stringPermutations';
import { stringPermutations, stringPermutationsGenerator } from './stringPermutations';
import { stringCombinations } from './stringCombinations';

export const transformations = {
Expand All @@ -59,5 +59,6 @@ export const transformations = {
numberToText,
reverseWordsInString,
stringPermutations,
stringPermutationsGenerator,
stringCombinations
};
Loading