range-pie 2.4.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +5 -3
- package/dist/py-range.d.ts +33 -10
- package/dist/py-range.js +60 -43
- package/package.json +23 -18
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -155,13 +155,14 @@ console.log(range.length); // 5
|
|
|
155
155
|
|
|
156
156
|
### at()
|
|
157
157
|
|
|
158
|
-
The 'at' method accepts a number as argument to
|
|
158
|
+
The 'at' method accepts a number as argument to get the value at the specified index in a range. Returns `undefined` if the index is out of range, perfectly matching `Array.prototype.at`. Negative indices count backwards from the end of the range.
|
|
159
159
|
|
|
160
160
|
```javascript
|
|
161
161
|
const range = new PyRange(1, 5); // [1, 2, 3, 4]
|
|
162
162
|
console.log(range.at(0)); // 1
|
|
163
163
|
console.log(range.at(2)); // 3
|
|
164
|
-
console.log(range.at(-1)); //
|
|
164
|
+
console.log(range.at(-1)); // 4 (negative index wraps from the end)
|
|
165
|
+
console.log(range.at(10)); // undefined (out of bounds)
|
|
165
166
|
```
|
|
166
167
|
|
|
167
168
|
### toString()
|
|
@@ -400,7 +401,7 @@ const array = [...range]; // [0, 1, 2]
|
|
|
400
401
|
|
|
401
402
|
### Proxy Access
|
|
402
403
|
|
|
403
|
-
Proxy access allows access to the array elements using bracket notation, just like a regular array.
|
|
404
|
+
Proxy access allows access to the array elements using bracket notation, just like a regular array. Out-of-bounds access returns `undefined`.
|
|
404
405
|
|
|
405
406
|
```javascript
|
|
406
407
|
const range = new PyRange(5);
|
|
@@ -408,6 +409,7 @@ const proxy = range.asProxy();
|
|
|
408
409
|
|
|
409
410
|
console.log(proxy[0]); // 0
|
|
410
411
|
console.log(proxy[3]); // 3
|
|
412
|
+
console.log(proxy[10]); // undefined
|
|
411
413
|
```
|
|
412
414
|
|
|
413
415
|
### TypeScript Support
|
package/dist/py-range.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ declare class PyRange implements Iterable<number> {
|
|
|
6
6
|
private _stop;
|
|
7
7
|
private _step;
|
|
8
8
|
private _length;
|
|
9
|
+
private static toIntegerOrInfinity;
|
|
10
|
+
private static isArrayIndexProperty;
|
|
9
11
|
/**
|
|
10
12
|
* Creates a new PyRange instance.
|
|
11
13
|
* @param {...number} args - The arguments of the range. The possible forms are
|
|
@@ -21,7 +23,9 @@ declare class PyRange implements Iterable<number> {
|
|
|
21
23
|
* @property {number} step - The step of the range.
|
|
22
24
|
* @property {number} length - The length of the range.
|
|
23
25
|
*/
|
|
24
|
-
constructor(
|
|
26
|
+
constructor(stop: number);
|
|
27
|
+
constructor(start: number, stop: number);
|
|
28
|
+
constructor(start: number, stop: number, step: number);
|
|
25
29
|
/**
|
|
26
30
|
* Gets the length of this range.
|
|
27
31
|
*
|
|
@@ -52,11 +56,17 @@ declare class PyRange implements Iterable<number> {
|
|
|
52
56
|
get step(): number;
|
|
53
57
|
/**
|
|
54
58
|
* Gets the value at the specified index in this range.
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
59
|
+
*
|
|
60
|
+
* Negative indices are supported and wrap from the end, matching the
|
|
61
|
+
* behaviour of `Array.prototype.at()`. For example, `at(-1)` returns the
|
|
62
|
+
* last element.
|
|
63
|
+
*
|
|
64
|
+
* @param {number} index - The index of the value to retrieve. Negative
|
|
65
|
+
* indices count from the end of the range.
|
|
66
|
+
* @returns {number|undefined} The value at the specified index, or
|
|
67
|
+
* undefined when the normalised index is out of range.
|
|
58
68
|
*/
|
|
59
|
-
at(index: number): number;
|
|
69
|
+
at(index: number): number | undefined;
|
|
60
70
|
/**
|
|
61
71
|
* Converts the range to a string.
|
|
62
72
|
* @returns {string} A string of the form `Range(start, stop, step)`.
|
|
@@ -71,6 +81,7 @@ declare class PyRange implements Iterable<number> {
|
|
|
71
81
|
* Validates that the callback is a function.
|
|
72
82
|
* @param {Function} cb - The callback to validate.
|
|
73
83
|
* @throws {TypeError} If the callback is not a function.
|
|
84
|
+
* @internal
|
|
74
85
|
*/
|
|
75
86
|
private static validateCb;
|
|
76
87
|
/**
|
|
@@ -92,15 +103,23 @@ declare class PyRange implements Iterable<number> {
|
|
|
92
103
|
filter(callback: (value: number, index: number, range: PyRange) => boolean): number[];
|
|
93
104
|
/**
|
|
94
105
|
* Reduces the range to a single value.
|
|
106
|
+
*
|
|
107
|
+
* Uses rest parameters to reliably distinguish between "no initial value
|
|
108
|
+
* provided" and "explicit `undefined` passed as initial value", matching
|
|
109
|
+
* the behaviour of `Array.prototype.reduce()`.
|
|
110
|
+
*
|
|
95
111
|
* @param {function(T, number, number, PyRange): T} callback - The callback
|
|
96
112
|
* function to apply to every element. The callback should take four
|
|
97
113
|
* arguments: the accumulator, the current value, the index of the current
|
|
98
114
|
* value, and the range object.
|
|
99
115
|
* @param {T} [initialValue] - The initial value of the accumulator.
|
|
100
116
|
* @returns {T} The final value of the accumulator.
|
|
117
|
+
* @throws {TypeError} If the range is empty and no initial value is provided.
|
|
101
118
|
* @template T
|
|
102
119
|
*/
|
|
103
|
-
reduce
|
|
120
|
+
reduce(callback: (accumulator: number, value: number, index: number, range: PyRange) => number): number;
|
|
121
|
+
reduce(callback: (accumulator: number, value: number, index: number, range: PyRange) => number, initialValue: number): number;
|
|
122
|
+
reduce<U>(callback: (accumulator: U, value: number, index: number, range: PyRange) => U, initialValue: U): U;
|
|
104
123
|
/**
|
|
105
124
|
* Determines whether at least one element of the range satisfies the
|
|
106
125
|
* provided test.
|
|
@@ -189,6 +208,10 @@ declare class PyRange implements Iterable<number> {
|
|
|
189
208
|
slice(begin?: number, end?: number): PyRange;
|
|
190
209
|
/**
|
|
191
210
|
* Reverses the order of the elements in this range, returning a new PyRange object.
|
|
211
|
+
*
|
|
212
|
+
* The new range starts at the last actual element of this range and steps
|
|
213
|
+
* in the opposite direction. The original range is not modified.
|
|
214
|
+
*
|
|
192
215
|
* @returns {PyRange} A new PyRange object with the elements in reverse order.
|
|
193
216
|
*/
|
|
194
217
|
reverse(): PyRange;
|
|
@@ -219,18 +242,18 @@ declare class PyRange implements Iterable<number> {
|
|
|
219
242
|
values(): IterableIterator<number>;
|
|
220
243
|
/**
|
|
221
244
|
* Implements the iterable protocol for this range.
|
|
222
|
-
* @returns {
|
|
245
|
+
* @returns {IterableIterator<number>} An iterator for this range.
|
|
223
246
|
*/
|
|
224
|
-
[Symbol.iterator]():
|
|
247
|
+
[Symbol.iterator](): IterableIterator<number>;
|
|
225
248
|
/**
|
|
226
249
|
* Returns a Proxy for this range, allowing indexed access.
|
|
227
250
|
* This proxy enables accessing range elements via array-like indexing.
|
|
228
251
|
* If the property is a number, it will return the element at that index.
|
|
229
252
|
*
|
|
230
|
-
* @returns {PyRange & { [key: number]: number }} A proxy for the PyRange instance.
|
|
253
|
+
* @returns {PyRange & { [key: number]: number | undefined }} A proxy for the PyRange instance.
|
|
231
254
|
*/
|
|
232
255
|
asProxy(): PyRange & {
|
|
233
|
-
[key: number]: number;
|
|
256
|
+
[key: number]: number | undefined;
|
|
234
257
|
};
|
|
235
258
|
}
|
|
236
259
|
export { PyRange };
|
package/dist/py-range.js
CHANGED
|
@@ -5,21 +5,22 @@ exports.PyRange = void 0;
|
|
|
5
5
|
* A class that simulates Python's range function, combined with several useful JavaScript array methods.
|
|
6
6
|
*/
|
|
7
7
|
class PyRange {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
8
|
+
_start;
|
|
9
|
+
_stop;
|
|
10
|
+
_step;
|
|
11
|
+
_length;
|
|
12
|
+
static toIntegerOrInfinity(value) {
|
|
13
|
+
const numeric = +value;
|
|
14
|
+
return Number.isNaN(numeric) ? 0 : Math.trunc(numeric);
|
|
15
|
+
}
|
|
16
|
+
static isArrayIndexProperty(prop) {
|
|
17
|
+
const index = Number(prop);
|
|
18
|
+
return (prop !== "" &&
|
|
19
|
+
Number.isInteger(index) &&
|
|
20
|
+
index >= 0 &&
|
|
21
|
+
index < 2 ** 32 - 1 &&
|
|
22
|
+
String(index) === prop);
|
|
23
|
+
}
|
|
23
24
|
constructor(...args) {
|
|
24
25
|
if (!args.every((arg) => typeof arg === "number")) {
|
|
25
26
|
throw new TypeError("All arguments must be numbers");
|
|
@@ -83,15 +84,23 @@ class PyRange {
|
|
|
83
84
|
}
|
|
84
85
|
/**
|
|
85
86
|
* Gets the value at the specified index in this range.
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
87
|
+
*
|
|
88
|
+
* Negative indices are supported and wrap from the end, matching the
|
|
89
|
+
* behaviour of `Array.prototype.at()`. For example, `at(-1)` returns the
|
|
90
|
+
* last element.
|
|
91
|
+
*
|
|
92
|
+
* @param {number} index - The index of the value to retrieve. Negative
|
|
93
|
+
* indices count from the end of the range.
|
|
94
|
+
* @returns {number|undefined} The value at the specified index, or
|
|
95
|
+
* undefined when the normalised index is out of range.
|
|
89
96
|
*/
|
|
90
97
|
at(index) {
|
|
91
|
-
|
|
92
|
-
|
|
98
|
+
const relativeIndex = PyRange.toIntegerOrInfinity(index);
|
|
99
|
+
const normalised = relativeIndex < 0 ? this._length + relativeIndex : relativeIndex;
|
|
100
|
+
if (normalised < 0 || normalised >= this._length) {
|
|
101
|
+
return undefined;
|
|
93
102
|
}
|
|
94
|
-
return this._start +
|
|
103
|
+
return this._start + normalised * this._step;
|
|
95
104
|
}
|
|
96
105
|
/**
|
|
97
106
|
* Converts the range to a string.
|
|
@@ -111,6 +120,7 @@ class PyRange {
|
|
|
111
120
|
* Validates that the callback is a function.
|
|
112
121
|
* @param {Function} cb - The callback to validate.
|
|
113
122
|
* @throws {TypeError} If the callback is not a function.
|
|
123
|
+
* @internal
|
|
114
124
|
*/
|
|
115
125
|
static validateCb(cb) {
|
|
116
126
|
if (typeof cb !== "function") {
|
|
@@ -151,25 +161,16 @@ class PyRange {
|
|
|
151
161
|
}
|
|
152
162
|
return result;
|
|
153
163
|
}
|
|
154
|
-
|
|
155
|
-
* Reduces the range to a single value.
|
|
156
|
-
* @param {function(T, number, number, PyRange): T} callback - The callback
|
|
157
|
-
* function to apply to every element. The callback should take four
|
|
158
|
-
* arguments: the accumulator, the current value, the index of the current
|
|
159
|
-
* value, and the range object.
|
|
160
|
-
* @param {T} [initialValue] - The initial value of the accumulator.
|
|
161
|
-
* @returns {T} The final value of the accumulator.
|
|
162
|
-
* @template T
|
|
163
|
-
*/
|
|
164
|
-
reduce(callback, initialValue) {
|
|
164
|
+
reduce(callback, ...args) {
|
|
165
165
|
PyRange.validateCb(callback);
|
|
166
|
-
|
|
166
|
+
const hasInitial = args.length > 0;
|
|
167
|
+
if (this._length === 0 && !hasInitial) {
|
|
167
168
|
throw new TypeError("Reduce of empty range with no initial value");
|
|
168
169
|
}
|
|
169
170
|
let accumulator;
|
|
170
171
|
let startIndex;
|
|
171
|
-
if (
|
|
172
|
-
accumulator =
|
|
172
|
+
if (hasInitial) {
|
|
173
|
+
accumulator = args[0];
|
|
173
174
|
startIndex = 0;
|
|
174
175
|
}
|
|
175
176
|
else {
|
|
@@ -373,13 +374,22 @@ class PyRange {
|
|
|
373
374
|
}
|
|
374
375
|
/**
|
|
375
376
|
* Reverses the order of the elements in this range, returning a new PyRange object.
|
|
377
|
+
*
|
|
378
|
+
* The new range starts at the last actual element of this range and steps
|
|
379
|
+
* in the opposite direction. The original range is not modified.
|
|
380
|
+
*
|
|
376
381
|
* @returns {PyRange} A new PyRange object with the elements in reverse order.
|
|
377
382
|
*/
|
|
378
383
|
reverse() {
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
384
|
+
if (this._length === 0) {
|
|
385
|
+
// Return an empty range with the same step direction — nothing to reverse
|
|
386
|
+
return new PyRange(this._start, this._start, this._step);
|
|
387
|
+
}
|
|
388
|
+
// The new start is the last actual element (not _stop which is exclusive)
|
|
389
|
+
const newStart = this.at(this._length - 1);
|
|
390
|
+
// The new stop is one step past the first element in the new direction
|
|
391
|
+
const newStop = this._start - this._step;
|
|
392
|
+
return new PyRange(newStart, newStop, -this._step);
|
|
383
393
|
}
|
|
384
394
|
/**
|
|
385
395
|
* Returns an iterator of `[index, value]` pairs for each element in the range.
|
|
@@ -418,7 +428,7 @@ class PyRange {
|
|
|
418
428
|
}
|
|
419
429
|
/**
|
|
420
430
|
* Implements the iterable protocol for this range.
|
|
421
|
-
* @returns {
|
|
431
|
+
* @returns {IterableIterator<number>} An iterator for this range.
|
|
422
432
|
*/
|
|
423
433
|
[Symbol.iterator]() {
|
|
424
434
|
let index = 0;
|
|
@@ -436,6 +446,9 @@ class PyRange {
|
|
|
436
446
|
return { value, done: false };
|
|
437
447
|
}
|
|
438
448
|
},
|
|
449
|
+
[Symbol.iterator]() {
|
|
450
|
+
return this;
|
|
451
|
+
},
|
|
439
452
|
};
|
|
440
453
|
}
|
|
441
454
|
/**
|
|
@@ -443,7 +456,7 @@ class PyRange {
|
|
|
443
456
|
* This proxy enables accessing range elements via array-like indexing.
|
|
444
457
|
* If the property is a number, it will return the element at that index.
|
|
445
458
|
*
|
|
446
|
-
* @returns {PyRange & { [key: number]: number }} A proxy for the PyRange instance.
|
|
459
|
+
* @returns {PyRange & { [key: number]: number | undefined }} A proxy for the PyRange instance.
|
|
447
460
|
*/
|
|
448
461
|
asProxy() {
|
|
449
462
|
return new Proxy(this, {
|
|
@@ -451,8 +464,12 @@ class PyRange {
|
|
|
451
464
|
if (typeof prop === "symbol") {
|
|
452
465
|
return Reflect.get(target, prop, receiver);
|
|
453
466
|
}
|
|
454
|
-
if (
|
|
455
|
-
|
|
467
|
+
if (PyRange.isArrayIndexProperty(prop)) {
|
|
468
|
+
const idx = Number(prop);
|
|
469
|
+
// Match Array behaviour: out-of-bounds returns undefined, not throw
|
|
470
|
+
if (idx >= target.length)
|
|
471
|
+
return undefined;
|
|
472
|
+
return target.at(idx);
|
|
456
473
|
}
|
|
457
474
|
return Reflect.get(target, prop, receiver);
|
|
458
475
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "range-pie",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "A TypeScript class that simulates Python's range function, combined with several useful JavaScript array methods.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|
|
@@ -22,25 +22,27 @@
|
|
|
22
22
|
],
|
|
23
23
|
"scripts": {
|
|
24
24
|
"build": "tsc",
|
|
25
|
-
"typecheck": "tsc --noEmit",
|
|
25
|
+
"typecheck": "tsc -p tsconfig.test.json --noEmit",
|
|
26
26
|
"build:prod": "tsc -p tsconfig.prod.json",
|
|
27
27
|
"build:watch": "tsc --watch",
|
|
28
28
|
"prepare": "husky",
|
|
29
29
|
"prepublishOnly": "npm run build:prod && npm run test",
|
|
30
30
|
"test": "jest",
|
|
31
|
+
"test:watch": "jest --watch",
|
|
32
|
+
"test:coverage": "jest --coverage",
|
|
31
33
|
"lint": "eslint . --ext .ts,.js",
|
|
32
34
|
"lint:fix": "eslint . --ext .ts,.js --fix",
|
|
33
|
-
"test:watch": "jest --watch",
|
|
34
35
|
"clean": "rimraf dist",
|
|
35
|
-
"format": "prettier --write
|
|
36
|
-
"format:check": "prettier --check
|
|
36
|
+
"format": "prettier --write .",
|
|
37
|
+
"format:check": "prettier --check .",
|
|
37
38
|
"example:basic": "node examples/basic-usage.js",
|
|
38
39
|
"example:array": "node examples/array-methods.js",
|
|
39
40
|
"example:advanced": "node examples/advanced-usage.js",
|
|
40
41
|
"example:ts": "ts-node examples/typescript-usage.ts",
|
|
41
42
|
"example:methods": "for file in examples/methods/*.ts; do echo \"\\n=== Running $file ===\"; ts-node \"$file\"; done",
|
|
42
43
|
"knip": "knip",
|
|
43
|
-
"
|
|
44
|
+
"lint-staged": "lint-staged",
|
|
45
|
+
"precommit": "npm run typecheck && npm run lint-staged"
|
|
44
46
|
},
|
|
45
47
|
"keywords": [
|
|
46
48
|
"range",
|
|
@@ -60,20 +62,23 @@
|
|
|
60
62
|
"url": "git+https://github.com/SkorpionG/range-pie.git"
|
|
61
63
|
},
|
|
62
64
|
"devDependencies": {
|
|
63
|
-
"@eslint/js": "^
|
|
65
|
+
"@eslint/js": "^10.0.1",
|
|
64
66
|
"@types/jest": "^30.0.0",
|
|
65
|
-
"@types/node": "^
|
|
66
|
-
"eslint": "^
|
|
67
|
-
"globals": "^17.
|
|
67
|
+
"@types/node": "^26.0.1",
|
|
68
|
+
"eslint": "^10.6.0",
|
|
69
|
+
"globals": "^17.7.0",
|
|
68
70
|
"husky": "^9.1.7",
|
|
69
|
-
"jest": "^30.
|
|
70
|
-
"knip": "^
|
|
71
|
-
"lint-staged": "^
|
|
72
|
-
"prettier": "^3.
|
|
71
|
+
"jest": "^30.4.2",
|
|
72
|
+
"knip": "^6.23.0",
|
|
73
|
+
"lint-staged": "^17.0.8",
|
|
74
|
+
"prettier": "^3.9.1",
|
|
73
75
|
"rimraf": "^6.1.3",
|
|
74
|
-
"ts-jest": "^29.4.
|
|
75
|
-
"ts-node": "^10.9.
|
|
76
|
-
"typescript": "^
|
|
77
|
-
"typescript-eslint": "^8.
|
|
76
|
+
"ts-jest": "^29.4.11",
|
|
77
|
+
"ts-node": "^10.9.2",
|
|
78
|
+
"typescript": "^6.0.3",
|
|
79
|
+
"typescript-eslint": "^8.62.0"
|
|
80
|
+
},
|
|
81
|
+
"overrides": {
|
|
82
|
+
"js-yaml": "^4.1.2"
|
|
78
83
|
}
|
|
79
84
|
}
|