Skip to content

Commit 0d98c9b

Browse files
committed
chore: improved interface and README
1 parent 0beccbb commit 0d98c9b

File tree

5 files changed

+128
-144
lines changed

5 files changed

+128
-144
lines changed

README.md

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,43 @@ npm install indexed-string-variation
2121

2222
## Usage
2323

24-
This library is ESM-only and written in TypeScript. You can import it as
24+
This library is ESM-only and written in TypeScript. You can import and use it as
2525
follows:
2626

2727
```js
28-
import { generator } from "indexed-string-variation";
28+
import isv from "indexed-string-variation";
2929

30-
const variations = generator("abc1");
30+
// Basic usage: generate all variations for a given alphabet
31+
for (
32+
const str of isv({ alphabet: "abc1", maxIterations: 23 })
33+
) {
34+
console.log(str);
35+
}
36+
37+
// Generate variations from a specific index (using BigInt)
38+
for (
39+
const str of isv({
40+
alphabet: "abc1",
41+
from: 20n,
42+
maxIterations: 5,
43+
})
44+
) {
45+
console.log(str);
46+
}
3147

32-
for (let i = 0; i < 23; i++) {
33-
console.log(i, variations(i)); // generates the i-th string in the alphabet 'abc1'
48+
// Generate variations up to a maximum string length
49+
for (const str of isv({ alphabet: "abc1", maxLen: 2 })) {
50+
console.log(str);
3451
}
3552

36-
// Using BigInt for large indices
37-
console.log(variations(12345678901234567890n));
53+
// endless variations (don't use a `for ... of` loop because it will never end!)
54+
const values = isv({
55+
alphabet: "abc1",
56+
});
57+
58+
console.log(values.next()); // { value: 'a', done: false }
59+
console.log(values.next()); // { value: 'b', done: false }
60+
//...
3861
```
3962

4063
## TypeScript

src/generate/generateBigInt.ts

Lines changed: 0 additions & 38 deletions
This file was deleted.

src/generate/generateInt.ts

Lines changed: 0 additions & 38 deletions
This file was deleted.

src/index.ts

Lines changed: 74 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,86 @@
11
export const defaultAlphabet =
22
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
33

4-
export function generator(alphabet?: string) {
5-
// remove duplicates from alphabets
6-
const cleanAlphabet = (alphabet: string) => {
7-
return Array.from(new Set(alphabet.split(''))).join('')
4+
function cleanAlphabet(alphabet: string) {
5+
return Array.from(new Set(alphabet.split(''))).join('')
6+
}
7+
8+
// calculates the level of a given index in the current virtual tree
9+
const getLevel = (base: bigint, index: bigint): bigint => {
10+
let level = 0n
11+
let current = index
12+
let parent: bigint
13+
while (current > 0n) {
14+
parent = (current - 1n) / base
15+
level++
16+
current = parent
817
}
18+
return level
19+
}
920

10-
if (typeof alphabet !== 'undefined' && typeof alphabet !== 'string') {
11-
throw new TypeError('alphabet must be a string')
21+
// Generates a string based on the given index and alphabet
22+
function generateString(startIndex: bigint, alphabet: string): string {
23+
if (startIndex === 0n) return ''
24+
const n = BigInt(alphabet.length)
25+
let result = ''
26+
let l: bigint
27+
let f: bigint
28+
let rebasedPos: bigint
29+
let rebasedIndex: bigint
30+
let index = startIndex
31+
while (index > 0n) {
32+
l = getLevel(n, index)
33+
f = 0n
34+
for (let i = 0n; i < l; i++) {
35+
f += n ** i
36+
}
37+
rebasedPos = index - f
38+
rebasedIndex = ((rebasedPos % n) + n) % n // ensure non-negative
39+
result = alphabet[Number(rebasedIndex)] + result
40+
index = (index - 1n) / n
1241
}
42+
return result
43+
}
1344

14-
const cleanedAlphabet = alphabet ? cleanAlphabet(alphabet) : defaultAlphabet
45+
export type GenOptions = {
46+
alphabet?: string
47+
from?: bigint
48+
to?: bigint
49+
maxLen?: number
50+
maxIterations?: number
51+
}
1552

16-
// string generation function
17-
const generate = (index: number | bigint) => {
18-
return typeof index === 'bigint'
19-
? generateBigInt(index, cleanedAlphabet)
20-
: generateInt(index as number, cleanedAlphabet)
21-
}
53+
export function* indexedStringVariation(options: GenOptions) {
54+
const alphabet = options.alphabet
55+
? cleanAlphabet(options.alphabet)
56+
: defaultAlphabet
57+
const from = options.from ?? 0n
2258

23-
generate.alphabet = cleanedAlphabet
59+
let iterations = 0
60+
let i = from
2461

25-
return generate
26-
}
62+
while (true) {
63+
const str = generateString(i, alphabet)
64+
65+
if (options.maxLen !== undefined && str.length > options.maxLen) {
66+
break
67+
}
68+
yield str
69+
70+
i++
71+
iterations++
2772

28-
import generateBigInt from './generate/generateBigInt.js'
29-
import generateInt from './generate/generateInt.js'
73+
if (options.to !== undefined && i > options.to) {
74+
break
75+
}
76+
77+
if (
78+
options.maxIterations !== undefined &&
79+
iterations >= options.maxIterations
80+
) {
81+
break
82+
}
83+
}
84+
}
3085

31-
export default generator
86+
export default indexedStringVariation

src/test.ts

Lines changed: 24 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { describe, expect, it } from 'vitest'
2-
import { defaultAlphabet, generator } from './index.js'
2+
import { indexedStringVariation, type GenOptions } from './index.js'
33

4-
const cases: [string, string, string[]][] = [
4+
const cases: [string, GenOptions, string[]][] = [
55
[
66
'it should produce variations with digits in alphabet',
7-
'0123456789',
7+
{ alphabet: '0123456789', from: 0n, to: 21n },
88
[
99
'',
1010
'0',
@@ -32,7 +32,7 @@ const cases: [string, string, string[]][] = [
3232
],
3333
[
3434
'it should produce variations with alphanumeric alphabet',
35-
'abc1',
35+
{ alphabet: 'abc1', from: 0n, to: 21n },
3636
[
3737
'',
3838
'a',
@@ -60,7 +60,7 @@ const cases: [string, string, string[]][] = [
6060
],
6161
[
6262
'it should remove duplicates from alphabet',
63-
'aabbbbcc1111111',
63+
{ alphabet: 'aabbbbcc1111111', from: 0n, to: 21n },
6464
[
6565
'',
6666
'a',
@@ -86,48 +86,30 @@ const cases: [string, string, string[]][] = [
8686
'aaa',
8787
],
8888
],
89+
[
90+
'it uses the default alphabet if none is provided',
91+
{ from: 0n, to: 10n },
92+
['', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'],
93+
],
94+
[
95+
'it stops at maxLen if provided',
96+
{ alphabet: 'ab', maxLen: 2 },
97+
['', 'a', 'b', 'aa', 'ab', 'ba', 'bb'],
98+
],
99+
[
100+
'it stops at maxIterations if provided',
101+
{ alphabet: 'ab', maxIterations: 5 },
102+
['', 'a', 'b', 'aa', 'ab'],
103+
],
89104
]
90105

91106
describe('indexed-string-variation', () => {
92107
it.each(cases)(
93108
'%s',
94-
(_title: string, alphabet: string, expected: string[]) => {
95-
const isvn = generator(alphabet)
96-
const generatedInt: string[] = []
97-
const generatedBigInt: string[] = []
98-
for (let i = 0; i < expected.length; i++) {
99-
generatedInt.push(isvn(i))
100-
generatedBigInt.push(isvn(BigInt(i)))
101-
}
102-
expect(generatedInt).toEqual(expected)
103-
expect(generatedBigInt).toEqual(expected)
109+
(_title: string, options: GenOptions, expected: string[]) => {
110+
const isvn = indexedStringVariation(options)
111+
const generatedStrings = [...isvn]
112+
expect(generatedStrings).toEqual(expected)
104113
},
105114
)
106-
107-
it('it must use the default alphabet if no alphabet is given', () => {
108-
const g = generator()
109-
expect(g.alphabet).toBe(defaultAlphabet)
110-
})
111-
112-
it('throws TypeError for invalid integer indices', () => {
113-
const isvn = generator('abc')
114-
// Not an integer
115-
expect(() => isvn(1.5)).toThrow(TypeError)
116-
expect(() => isvn(Number.NaN)).toThrow(TypeError)
117-
expect(() => isvn(Number.POSITIVE_INFINITY)).toThrow(TypeError)
118-
// Negative number
119-
expect(() => isvn(-1)).toThrow(TypeError)
120-
// Negative BigInt
121-
expect(() => isvn(BigInt(-1))).toThrow(TypeError)
122-
// Not a number or bigint
123-
expect(() => isvn('foo' as unknown as number)).toThrow(TypeError)
124-
})
125-
126-
it('throws TypeError if an invalid alphabet is passed', () => {
127-
expect(() => generator(123 as unknown as string)).toThrow(TypeError)
128-
expect(() => generator({} as unknown as string)).toThrow(TypeError)
129-
expect(() => generator([] as unknown as string)).toThrow(TypeError)
130-
expect(() => generator(null as unknown as string)).toThrow(TypeError)
131-
expect(() => generator(undefined)).not.toThrow() // undefined is allowed (will use the default alphabet)
132-
})
133115
})

0 commit comments

Comments
 (0)