diff --git a/.size-limit.json b/.size-limit.json index 3876dd09..c3acaeaa 100644 --- a/.size-limit.json +++ b/.size-limit.json @@ -2,42 +2,42 @@ { "name": "es5-full", "path": "lib/dist/es5/mod/ts-utils.js", - "limit": "24.5 kb", + "limit": "27.5 kb", "brotli": false, "running": false }, { "name": "es6-full", "path": "lib/dist/es6/mod/ts-utils.js", - "limit": "23.5 kb", + "limit": "26.5 kb", "brotli": false, "running": false }, { "name": "es5-full-brotli", "path": "lib/dist/es5/mod/ts-utils.js", - "limit": "9 kb", + "limit": "10 kb", "brotli": true, "running": false }, { "name": "es6-full-brotli", "path": "lib/dist/es6/mod/ts-utils.js", - "limit": "9 kb", + "limit": "9.75 kb", "brotli": true, "running": false }, { "name": "es5-zip", "path": "lib/dist/es5/mod/ts-utils.js", - "limit": "9.5 Kb", + "limit": "11 Kb", "gzip": true, "running": false }, { "name": "es6-zip", "path": "lib/dist/es6/mod/ts-utils.js", - "limit": "9.5 Kb", + "limit": "10.5 Kb", "gzip": true, "running": false }, @@ -68,7 +68,7 @@ { "name": "es5-poly", "path": "lib/bundle/es5/ts-polyfills-utils.js", - "limit": "9 kb", + "limit": "10 kb", "brotli": false, "running": false }, diff --git a/README.md b/README.md index 8d7330d8..c6953ca8 100644 --- a/README.md +++ b/README.md @@ -108,12 +108,12 @@ Below is a categorized list of all available utilities with direct links to thei | Type | Functions / Helpers / Aliases / Polyfills |----------------------------|--------------------------------------------------- | Runtime Environment Checks | [getCancelIdleCallback](https://nevware21.github.io/ts-utils/typedoc/functions/getCancelIdleCallback.html)(); [getDocument](https://nevware21.github.io/ts-utils/typedoc/functions/getDocument.html)(); [getGlobal](https://nevware21.github.io/ts-utils/typedoc/functions/getGlobal.html)(); [getHistory](https://nevware21.github.io/ts-utils/typedoc/functions/getHistory.html)(); [getIdleCallback](https://nevware21.github.io/ts-utils/typedoc/functions/getIdleCallback.html)(); [getInst](https://nevware21.github.io/ts-utils/typedoc/functions/getInst.html)(); [getNavigator](https://nevware21.github.io/ts-utils/typedoc/functions/getNavigator.html)(); [getPerformance](https://nevware21.github.io/ts-utils/typedoc/functions/getPerformance.html)(); [getWindow](https://nevware21.github.io/ts-utils/typedoc/functions/getWindow.html)(); [hasDocument](https://nevware21.github.io/ts-utils/typedoc/functions/hasDocument.html)(); [hasHistory](https://nevware21.github.io/ts-utils/typedoc/functions/hasHistory.html)(); [hasNavigator](https://nevware21.github.io/ts-utils/typedoc/functions/hasNavigator.html)(); [hasPerformance](https://nevware21.github.io/ts-utils/typedoc/functions/hasPerformance.html)(); [hasWindow](https://nevware21.github.io/ts-utils/typedoc/functions/hasWindow.html)(); [isNode](https://nevware21.github.io/ts-utils/typedoc/functions/isNode.html)(); [isWebWorker](https://nevware21.github.io/ts-utils/typedoc/functions/isWebWorker.html)(); [hasIdleCallback](https://nevware21.github.io/ts-utils/typedoc/functions/hasIdleCallback.html)(); [lazySafeGetInst](https://nevware21.github.io/ts-utils/typedoc/functions/lazySafeGetInst.html)(); -| Type Identity | [isArray](https://nevware21.github.io/ts-utils/typedoc/functions/isArray.html)(); [isArrayBuffer](https://nevware21.github.io/ts-utils/typedoc/functions/isArrayBuffer.html)(); [isAsyncFunction](https://nevware21.github.io/ts-utils/typedoc/functions/isAsyncFunction.html)(); [isAsyncGenerator](https://nevware21.github.io/ts-utils/typedoc/functions/isAsyncGenerator.html)(); [isBigInt](https://nevware21.github.io/ts-utils/typedoc/functions/isBigInt.html)(); [isBlob](https://nevware21.github.io/ts-utils/typedoc/functions/isBlob.html)(); [isBoolean](https://nevware21.github.io/ts-utils/typedoc/functions/isBoolean.html)(); [isDate](https://nevware21.github.io/ts-utils/typedoc/functions/isDate.html)(); [isElement](https://nevware21.github.io/ts-utils/typedoc/functions/isElement.html)(); [isElementLike](https://nevware21.github.io/ts-utils/typedoc/functions/isElementLike.html)(); [isError](https://nevware21.github.io/ts-utils/typedoc/functions/isError.html)(); [isFile](https://nevware21.github.io/ts-utils/typedoc/functions/isFile.html)(); [isFiniteNumber](https://nevware21.github.io/ts-utils/typedoc/functions/isFiniteNumber.html)(); [isFormData](https://nevware21.github.io/ts-utils/typedoc/functions/isFormData.html)(); [isFunction](https://nevware21.github.io/ts-utils/typedoc/functions/isFunction.html)(); [isGenerator](https://nevware21.github.io/ts-utils/typedoc/functions/isGenerator.html)(); [isInteger](https://nevware21.github.io/ts-utils/typedoc/functions/isInteger.html)(); [isIterable](https://nevware21.github.io/ts-utils/typedoc/functions/isIterable.html)(); [isIterator](https://nevware21.github.io/ts-utils/typedoc/functions/isIterator.html)(); [isMap](https://nevware21.github.io/ts-utils/typedoc/functions/isMap.html)(); [isMapLike](https://nevware21.github.io/ts-utils/typedoc/functions/isMapLike.html)(); [isNullOrUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isNullOrUndefined.html)(); [isNumber](https://nevware21.github.io/ts-utils/typedoc/functions/isNumber.html)(); [isObject](https://nevware21.github.io/ts-utils/typedoc/functions/isObject.html)(); [isPlainObject](https://nevware21.github.io/ts-utils/typedoc/functions/isPlainObject.html)(); [isPrimitive](https://nevware21.github.io/ts-utils/typedoc/functions/isPrimitive.html)(); [isPrimitiveType](https://nevware21.github.io/ts-utils/typedoc/functions/isPrimitiveType.html)(); [isPromise](https://nevware21.github.io/ts-utils/typedoc/functions/isPromise.html)(); [isPromiseLike](https://nevware21.github.io/ts-utils/typedoc/functions/isPromiseLike.html)(); [isRegExp](https://nevware21.github.io/ts-utils/typedoc/functions/isRegExp.html)(); [isSet](https://nevware21.github.io/ts-utils/typedoc/functions/isSet.html)(); [isSetLike](https://nevware21.github.io/ts-utils/typedoc/functions/isSetLike.html)(); [isStrictNullOrUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isStrictNullOrUndefined.html)(); [isStrictUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isStrictUndefined.html)(); [isString](https://nevware21.github.io/ts-utils/typedoc/functions/isString.html)(); [isThenable](https://nevware21.github.io/ts-utils/typedoc/functions/isThenable.html)(); [isTypeof](https://nevware21.github.io/ts-utils/typedoc/functions/isTypeof.html)(); [isUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isUndefined.html)(); [isWeakMap](https://nevware21.github.io/ts-utils/typedoc/functions/isWeakMap.html)(); [isWeakSet](https://nevware21.github.io/ts-utils/typedoc/functions/isWeakSet.html)(); +| Type Identity | [isArray](https://nevware21.github.io/ts-utils/typedoc/functions/isArray.html)(); [isArrayLike](https://nevware21.github.io/ts-utils/typedoc/functions/isArrayLike.html)(); [isArrayBuffer](https://nevware21.github.io/ts-utils/typedoc/functions/isArrayBuffer.html)(); [isAsyncFunction](https://nevware21.github.io/ts-utils/typedoc/functions/isAsyncFunction.html)(); [isAsyncGenerator](https://nevware21.github.io/ts-utils/typedoc/functions/isAsyncGenerator.html)(); [isBigInt](https://nevware21.github.io/ts-utils/typedoc/functions/isBigInt.html)(); [isBlob](https://nevware21.github.io/ts-utils/typedoc/functions/isBlob.html)(); [isBoolean](https://nevware21.github.io/ts-utils/typedoc/functions/isBoolean.html)(); [isDate](https://nevware21.github.io/ts-utils/typedoc/functions/isDate.html)(); [isElement](https://nevware21.github.io/ts-utils/typedoc/functions/isElement.html)(); [isElementLike](https://nevware21.github.io/ts-utils/typedoc/functions/isElementLike.html)(); [isError](https://nevware21.github.io/ts-utils/typedoc/functions/isError.html)(); [isFile](https://nevware21.github.io/ts-utils/typedoc/functions/isFile.html)(); [isFiniteNumber](https://nevware21.github.io/ts-utils/typedoc/functions/isFiniteNumber.html)(); [isFormData](https://nevware21.github.io/ts-utils/typedoc/functions/isFormData.html)(); [isFunction](https://nevware21.github.io/ts-utils/typedoc/functions/isFunction.html)(); [isGenerator](https://nevware21.github.io/ts-utils/typedoc/functions/isGenerator.html)(); [isInteger](https://nevware21.github.io/ts-utils/typedoc/functions/isInteger.html)(); [isIterable](https://nevware21.github.io/ts-utils/typedoc/functions/isIterable.html)(); [isIterator](https://nevware21.github.io/ts-utils/typedoc/functions/isIterator.html)(); [isMap](https://nevware21.github.io/ts-utils/typedoc/functions/isMap.html)(); [isMapLike](https://nevware21.github.io/ts-utils/typedoc/functions/isMapLike.html)(); [isNullOrUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isNullOrUndefined.html)(); [isNumber](https://nevware21.github.io/ts-utils/typedoc/functions/isNumber.html)(); [isObject](https://nevware21.github.io/ts-utils/typedoc/functions/isObject.html)(); [isPlainObject](https://nevware21.github.io/ts-utils/typedoc/functions/isPlainObject.html)(); [isPrimitive](https://nevware21.github.io/ts-utils/typedoc/functions/isPrimitive.html)(); [isPrimitiveType](https://nevware21.github.io/ts-utils/typedoc/functions/isPrimitiveType.html)(); [isPromise](https://nevware21.github.io/ts-utils/typedoc/functions/isPromise.html)(); [isPromiseLike](https://nevware21.github.io/ts-utils/typedoc/functions/isPromiseLike.html)(); [isRegExp](https://nevware21.github.io/ts-utils/typedoc/functions/isRegExp.html)(); [isSet](https://nevware21.github.io/ts-utils/typedoc/functions/isSet.html)(); [isSetLike](https://nevware21.github.io/ts-utils/typedoc/functions/isSetLike.html)(); [isStrictNullOrUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isStrictNullOrUndefined.html)(); [isStrictUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isStrictUndefined.html)(); [isString](https://nevware21.github.io/ts-utils/typedoc/functions/isString.html)(); [isThenable](https://nevware21.github.io/ts-utils/typedoc/functions/isThenable.html)(); [isTypeof](https://nevware21.github.io/ts-utils/typedoc/functions/isTypeof.html)(); [isUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isUndefined.html)(); [isWeakMap](https://nevware21.github.io/ts-utils/typedoc/functions/isWeakMap.html)(); [isWeakSet](https://nevware21.github.io/ts-utils/typedoc/functions/isWeakSet.html)(); | Value Check | [hasValue](https://nevware21.github.io/ts-utils/typedoc/functions/hasValue.html)(); [isDefined](https://nevware21.github.io/ts-utils/typedoc/functions/isDefined.html)(); [isEmpty](https://nevware21.github.io/ts-utils/typedoc/functions/isEmpty.html)(); [isNotTruthy](https://nevware21.github.io/ts-utils/typedoc/functions/isNotTruthy.html)(); [isNullOrUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isNullOrUndefined.html)(); [isStrictNullOrUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isStrictNullOrUndefined.html)(); [isStrictUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isStrictUndefined.html)(); [isTruthy](https://nevware21.github.io/ts-utils/typedoc/functions/isTruthy.html)(); [isUndefined](https://nevware21.github.io/ts-utils/typedoc/functions/isUndefined.html)(); | Value | [getValueByKey](https://nevware21.github.io/ts-utils/typedoc/functions/getValueByKey.html)(); [setValueByKey](https://nevware21.github.io/ts-utils/typedoc/functions/setValueByKey.html)(); [getValueByIter](https://nevware21.github.io/ts-utils/typedoc/functions/getValueByIter.html)(); [setValueByIter](https://nevware21.github.io/ts-utils/typedoc/functions/setValueByIter.html)(); [encodeAsJson](https://nevware21.github.io/ts-utils/typedoc/functions/encodeAsJson.html)(); [encodeAsHtml](https://nevware21.github.io/ts-utils/typedoc/functions/encodeAsHtml.html)(); [asString](https://nevware21.github.io/ts-utils/typedoc/functions/asString.html)(); [getIntValue](https://nevware21.github.io/ts-utils/typedoc/functions/getIntValue.html)(); [normalizeJsName](https://nevware21.github.io/ts-utils/typedoc/functions/normalizeJsName.html)(); |   |   -| Array | [arrAppend](https://nevware21.github.io/ts-utils/typedoc/functions/arrAppend.html)(); [arrContains](https://nevware21.github.io/ts-utils/typedoc/functions/arrContains.html)(); [arrEvery](https://nevware21.github.io/ts-utils/typedoc/functions/arrEvery.html)(); [arrFilter](https://nevware21.github.io/ts-utils/typedoc/functions/arrFilter.html)(); [arrFind](https://nevware21.github.io/ts-utils/typedoc/functions/arrFind.html)(); [arrFindIndex](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindIndex.html)(); [arrFindLast](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindLast.html)(); [arrFindLastIndex](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindLastIndex.html)(); [arrForEach](https://nevware21.github.io/ts-utils/typedoc/functions/arrForEach.html)(); [arrFrom](https://nevware21.github.io/ts-utils/typedoc/functions/arrFrom.html)(); [arrIncludes](https://nevware21.github.io/ts-utils/typedoc/functions/arrIncludes.html)(); [arrIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/arrIndexOf.html)(); [arrLastIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/arrLastIndexOf.html)(); [arrMap](https://nevware21.github.io/ts-utils/typedoc/functions/arrMap.html)(); [arrReduce](https://nevware21.github.io/ts-utils/typedoc/functions/arrReduce.html)(); [arrSlice](https://nevware21.github.io/ts-utils/typedoc/functions/arrSlice.html)(); [arrSome](https://nevware21.github.io/ts-utils/typedoc/functions/arrSome.html)(); [getLength](https://nevware21.github.io/ts-utils/typedoc/functions/getLength.html)(); [isArray](https://nevware21.github.io/ts-utils/typedoc/functions/isArray.html)();
[polyIsArray](https://nevware21.github.io/ts-utils/typedoc/functions/polyIsArray.html)(); [polyArrIncludes](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrIncludes.html)(); [polyArrFind](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFind.html)(); [polyArrFindIndex](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFindIndex.html)(); [polyArrFindLastIndex](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFindLastIndex.html)(); [polyArrFindLast](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFindLast.html)(); [polyArrFindLastIndex](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFindLastIndex.html)(); [polyArrFrom](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFrom.html)();
-| ArrayLike | [arrContains](https://nevware21.github.io/ts-utils/typedoc/functions/arrContains.html)(); [arrEvery](https://nevware21.github.io/ts-utils/typedoc/functions/arrEvery.html)(); [arrFilter](https://nevware21.github.io/ts-utils/typedoc/functions/arrFilter.html)(); [arrFind](https://nevware21.github.io/ts-utils/typedoc/functions/arrFind.html)(); [arrFindIndex](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindIndex.html)(); [arrFindLast](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindLast.html)(); [arrFindLastIndex](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindLastIndex.html)(); [arrForEach](https://nevware21.github.io/ts-utils/typedoc/functions/arrForEach.html)(); [arrFrom](https://nevware21.github.io/ts-utils/typedoc/functions/arrFrom.html)(); [arrIncludes](https://nevware21.github.io/ts-utils/typedoc/functions/arrIncludes.html)(); [arrIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/arrIndexOf.html)(); [arrLastIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/arrLastIndexOf.html)(); [arrMap](https://nevware21.github.io/ts-utils/typedoc/functions/arrMap.html)(); [arrReduce](https://nevware21.github.io/ts-utils/typedoc/functions/arrReduce.html)(); [arrSlice](https://nevware21.github.io/ts-utils/typedoc/functions/arrSlice.html)(); [arrSome](https://nevware21.github.io/ts-utils/typedoc/functions/arrSome.html)(); [getLength](https://nevware21.github.io/ts-utils/typedoc/functions/getLength.html)(); [objEntries](https://nevware21.github.io/ts-utils/typedoc/functions/objEntries.html)(); [objValues](https://nevware21.github.io/ts-utils/typedoc/functions/objValues.html)(); +| Array | [arrAppend](https://nevware21.github.io/ts-utils/typedoc/functions/arrAppend.html)(); [arrAt](https://nevware21.github.io/ts-utils/typedoc/functions/arrAt.html)(); [arrChunk](https://nevware21.github.io/ts-utils/typedoc/functions/arrChunk.html)(); [arrCompact](https://nevware21.github.io/ts-utils/typedoc/functions/arrCompact.html)(); [arrContains](https://nevware21.github.io/ts-utils/typedoc/functions/arrContains.html)(); [arrDifference](https://nevware21.github.io/ts-utils/typedoc/functions/arrDifference.html)(); [arrDrop](https://nevware21.github.io/ts-utils/typedoc/functions/arrDrop.html)(); [arrDropWhile](https://nevware21.github.io/ts-utils/typedoc/functions/arrDropWhile.html)(); [arrEvery](https://nevware21.github.io/ts-utils/typedoc/functions/arrEvery.html)(); [arrFilter](https://nevware21.github.io/ts-utils/typedoc/functions/arrFilter.html)(); [arrFill](https://nevware21.github.io/ts-utils/typedoc/functions/arrFill.html)(); [arrFind](https://nevware21.github.io/ts-utils/typedoc/functions/arrFind.html)(); [arrFindIndex](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindIndex.html)(); [arrFindLast](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindLast.html)(); [arrFindLastIndex](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindLastIndex.html)(); [arrFlatten](https://nevware21.github.io/ts-utils/typedoc/functions/arrFlatten.html)(); [arrForEach](https://nevware21.github.io/ts-utils/typedoc/functions/arrForEach.html)(); [arrFrom](https://nevware21.github.io/ts-utils/typedoc/functions/arrFrom.html)(); [arrGroupBy](https://nevware21.github.io/ts-utils/typedoc/functions/arrGroupBy.html)(); [arrIncludes](https://nevware21.github.io/ts-utils/typedoc/functions/arrIncludes.html)(); [arrIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/arrIndexOf.html)(); [arrIntersection](https://nevware21.github.io/ts-utils/typedoc/functions/arrIntersection.html)(); [arrLastIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/arrLastIndexOf.html)(); [arrMap](https://nevware21.github.io/ts-utils/typedoc/functions/arrMap.html)(); [arrPartition](https://nevware21.github.io/ts-utils/typedoc/functions/arrPartition.html)(); [arrReduce](https://nevware21.github.io/ts-utils/typedoc/functions/arrReduce.html)(); [arrReverse](https://nevware21.github.io/ts-utils/typedoc/functions/arrReverse.html)(); [arrRotate](https://nevware21.github.io/ts-utils/typedoc/functions/arrRotate.html)(); [arrSample](https://nevware21.github.io/ts-utils/typedoc/functions/arrSample.html)(); [arrShuffle](https://nevware21.github.io/ts-utils/typedoc/functions/arrShuffle.html)(); [arrSlice](https://nevware21.github.io/ts-utils/typedoc/functions/arrSlice.html)(); [arrSome](https://nevware21.github.io/ts-utils/typedoc/functions/arrSome.html)(); [arrTake](https://nevware21.github.io/ts-utils/typedoc/functions/arrTake.html)(); [arrTakeWhile](https://nevware21.github.io/ts-utils/typedoc/functions/arrTakeWhile.html)(); [arrUnion](https://nevware21.github.io/ts-utils/typedoc/functions/arrUnion.html)(); [arrUnique](https://nevware21.github.io/ts-utils/typedoc/functions/arrUnique.html)(); [arrUnzip](https://nevware21.github.io/ts-utils/typedoc/functions/arrUnzip.html)(); [arrWith](https://nevware21.github.io/ts-utils/typedoc/functions/arrWith.html)(); [arrZip](https://nevware21.github.io/ts-utils/typedoc/functions/arrZip.html)(); [getLength](https://nevware21.github.io/ts-utils/typedoc/functions/getLength.html)(); [isArray](https://nevware21.github.io/ts-utils/typedoc/functions/isArray.html)();
[polyIsArray](https://nevware21.github.io/ts-utils/typedoc/functions/polyIsArray.html)(); [polyArrIncludes](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrIncludes.html)(); [polyArrFind](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFind.html)(); [polyArrFindIndex](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFindIndex.html)(); [polyArrFindLast](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFindLast.html)(); [polyArrFindLastIndex](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFindLastIndex.html)(); [polyArrFrom](https://nevware21.github.io/ts-utils/typedoc/functions/polyArrFrom.html)();
+| ArrayLike | [arrAt](https://nevware21.github.io/ts-utils/typedoc/functions/arrAt.html)(); [arrChunk](https://nevware21.github.io/ts-utils/typedoc/functions/arrChunk.html)(); [arrCompact](https://nevware21.github.io/ts-utils/typedoc/functions/arrCompact.html)(); [arrContains](https://nevware21.github.io/ts-utils/typedoc/functions/arrContains.html)(); [arrDifference](https://nevware21.github.io/ts-utils/typedoc/functions/arrDifference.html)(); [arrDrop](https://nevware21.github.io/ts-utils/typedoc/functions/arrDrop.html)(); [arrDropWhile](https://nevware21.github.io/ts-utils/typedoc/functions/arrDropWhile.html)(); [arrEvery](https://nevware21.github.io/ts-utils/typedoc/functions/arrEvery.html)(); [arrFilter](https://nevware21.github.io/ts-utils/typedoc/functions/arrFilter.html)(); [arrFill](https://nevware21.github.io/ts-utils/typedoc/functions/arrFill.html)(); [arrFind](https://nevware21.github.io/ts-utils/typedoc/functions/arrFind.html)(); [arrFindIndex](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindIndex.html)(); [arrFindLast](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindLast.html)(); [arrFindLastIndex](https://nevware21.github.io/ts-utils/typedoc/functions/arrFindLastIndex.html)(); [arrFlatten](https://nevware21.github.io/ts-utils/typedoc/functions/arrFlatten.html)(); [arrForEach](https://nevware21.github.io/ts-utils/typedoc/functions/arrForEach.html)(); [arrFrom](https://nevware21.github.io/ts-utils/typedoc/functions/arrFrom.html)(); [arrGroupBy](https://nevware21.github.io/ts-utils/typedoc/functions/arrGroupBy.html)(); [arrIncludes](https://nevware21.github.io/ts-utils/typedoc/functions/arrIncludes.html)(); [arrIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/arrIndexOf.html)(); [arrIntersection](https://nevware21.github.io/ts-utils/typedoc/functions/arrIntersection.html)(); [arrLastIndexOf](https://nevware21.github.io/ts-utils/typedoc/functions/arrLastIndexOf.html)(); [arrMap](https://nevware21.github.io/ts-utils/typedoc/functions/arrMap.html)(); [arrPartition](https://nevware21.github.io/ts-utils/typedoc/functions/arrPartition.html)(); [arrReduce](https://nevware21.github.io/ts-utils/typedoc/functions/arrReduce.html)(); [arrReverse](https://nevware21.github.io/ts-utils/typedoc/functions/arrReverse.html)(); [arrRotate](https://nevware21.github.io/ts-utils/typedoc/functions/arrRotate.html)(); [arrSample](https://nevware21.github.io/ts-utils/typedoc/functions/arrSample.html)(); [arrShuffle](https://nevware21.github.io/ts-utils/typedoc/functions/arrShuffle.html)(); [arrSlice](https://nevware21.github.io/ts-utils/typedoc/functions/arrSlice.html)(); [arrSome](https://nevware21.github.io/ts-utils/typedoc/functions/arrSome.html)(); [arrTake](https://nevware21.github.io/ts-utils/typedoc/functions/arrTake.html)(); [arrTakeWhile](https://nevware21.github.io/ts-utils/typedoc/functions/arrTakeWhile.html)(); [arrUnion](https://nevware21.github.io/ts-utils/typedoc/functions/arrUnion.html)(); [arrUnique](https://nevware21.github.io/ts-utils/typedoc/functions/arrUnique.html)(); [arrUnzip](https://nevware21.github.io/ts-utils/typedoc/functions/arrUnzip.html)(); [arrWith](https://nevware21.github.io/ts-utils/typedoc/functions/arrWith.html)(); [arrZip](https://nevware21.github.io/ts-utils/typedoc/functions/arrZip.html)(); [getLength](https://nevware21.github.io/ts-utils/typedoc/functions/getLength.html)(); [objEntries](https://nevware21.github.io/ts-utils/typedoc/functions/objEntries.html)(); [objValues](https://nevware21.github.io/ts-utils/typedoc/functions/objValues.html)(); | DOM | [isElement](https://nevware21.github.io/ts-utils/typedoc/functions/isElement.html)(); [isElementLike](https://nevware21.github.io/ts-utils/typedoc/functions/isElementLike.html)(); | Enum | [createEnum](https://nevware21.github.io/ts-utils/typedoc/functions/createEnum.html)(); [createEnumKeyMap](https://nevware21.github.io/ts-utils/typedoc/functions/createEnumKeyMap.html)(); [createEnumValueMap](https://nevware21.github.io/ts-utils/typedoc/functions/createEnumValueMap.html)(); [createSimpleMap](https://nevware21.github.io/ts-utils/typedoc/functions/createSimpleMap.html)(); [createTypeMap](https://nevware21.github.io/ts-utils/typedoc/functions/createTypeMap.html)(); | Error | [createCustomError](https://nevware21.github.io/ts-utils/typedoc/functions/createCustomError.html)(); [isError](https://nevware21.github.io/ts-utils/typedoc/functions/isError.html)(); [throwError](https://nevware21.github.io/ts-utils/typedoc/functions/throwError.html)(); [throwRangeError](https://nevware21.github.io/ts-utils/typedoc/functions/throwRangeError.html)(); [throwTypeError](https://nevware21.github.io/ts-utils/typedoc/functions/throwTypeError.html)(); [throwUnsupported](https://nevware21.github.io/ts-utils/typedoc/functions/throwUnsupported.html)(); diff --git a/lib/src/array/at.ts b/lib/src/array/at.ts new file mode 100644 index 00000000..297e448e --- /dev/null +++ b/lib/src/array/at.ts @@ -0,0 +1,71 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { ArrProto } from "../internal/constants"; +import { _unwrapFunctionWithPoly } from "../internal/unwrapFunction"; +import { isArrayLike } from "../helpers/base"; + +/** + * The arrAt() method takes an integer value and returns the item at that index, allowing for + * positive and negative integers. Negative integers count back from the last item in the array. + * This is an ES2022 feature with polyfill support for older environments. + * @function + * @since 0.14.0 + * @group Array + * @group ArrayLike + * @typeParam T - Identifies the type of array elements + * @param theArray - The array or array-like object to get element from + * @param index - The index of the element to return. Negative index counts from the end + * @returns The element at the specified index, or undefined if index is out of range + * @example + * ```ts + * arrAt([1, 2, 3, 4, 5], 0); // 1 + * arrAt([1, 2, 3, 4, 5], 2); // 3 + * arrAt([1, 2, 3, 4, 5], -1); // 5 + * arrAt([1, 2, 3, 4, 5], -2); // 4 + * arrAt([1, 2, 3], 10); // undefined + * arrAt([1, 2, 3], -10); // undefined + * + * // Array-like objects + * arrAt({ length: 3, 0: "a", 1: "b", 2: "c" }, -1); // "c" + * ``` + */ +export const arrAt = (/*#__PURE__*/_unwrapFunctionWithPoly("at", ArrProto as any, polyArrAt) as (theArray: ArrayLike, index: number) => T | undefined); + +/** + * Polyfill implementation of Array.at() for environments that don't support it. + * @since 0.14.0 + * @group Array + * @group ArrayLike + * @group Polyfill + * @typeParam T - Identifies the type of array elements + * @param theArray - The array or array-like object to get element from + * @param index - The index of the element to return + * @returns The element at the specified index, or undefined if out of range + */ +/*#__NO_SIDE_EFFECTS__*/ +export function polyArrAt(theArray: ArrayLike, index: number): T | undefined { + let result: T | undefined; + + if (isArrayLike(theArray)) { + const len = theArray.length; + let idx = index; + + // Convert negative index to positive + if (idx < 0) { + idx = len + idx; + } + + // Check bounds and get value + if (idx >= 0 && idx < len) { + result = theArray[idx]; + } + } + + return result; +} diff --git a/lib/src/array/callbacks.ts b/lib/src/array/callbacks.ts index 9845cc26..a34c9e0d 100644 --- a/lib/src/array/callbacks.ts +++ b/lib/src/array/callbacks.ts @@ -14,8 +14,8 @@ * @group ArrayLike * @typeParam T - Identifies the type of array elements * @typeParam E - Identifies the type of the return array elements (defaults to T) - * @param value - The cuirrent element of the array being processed. - * @param index - The index of the current elemety of the array being processed. + * @param value - The current element of the array being processed. + * @param index - The index of the current element of the array being processed. * @param array - The array being processed. * @returns A boolean value indicating that the value is of the type expected (the test is true) */ @@ -29,8 +29,8 @@ export type ArrPredicateCallbackFn = (value: T, index: number, a * @group ArrayLike * @typeParam T - Identifies the type of array elements * @typeParam E - Identifies the type of the return array elements (defaults to T) - * @param value - The cuirrent element of the array being processed. - * @param index - The index of the current elemety of the array being processed. + * @param value - The current element of the array being processed. + * @param index - The index of the current element of the array being processed. * @param array - The array being processed. */ export type ArrPredicateCallbackFn2 = (value: T, index: number, array: T[]) => unknown; diff --git a/lib/src/array/chunk.ts b/lib/src/array/chunk.ts new file mode 100644 index 00000000..19aa3624 --- /dev/null +++ b/lib/src/array/chunk.ts @@ -0,0 +1,56 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { isArrayLike } from "../helpers/base"; +import { arrForEach } from "./forEach"; + +/** + * The arrChunk() method returns a new array with elements divided into groups of a specified size. + * The last group may have fewer elements if the array length is not divisible by the chunk size. + * @function + * @since 0.14.0 + * @group Array + * @group ArrayLike + * @typeParam T - Identifies the base type of array elements + * @param theArray - The array or array-like object to chunk + * @param size - The size of each chunk. Must be a positive integer + * @returns A new array of chunks, where each chunk is an array of the specified size + * @example + * ```ts + * arrChunk([1, 2, 3, 4, 5, 6, 7], 2); // [[1, 2], [3, 4], [5, 6], [7]] + * arrChunk([1, 2, 3, 4, 5], 3); // [[1, 2, 3], [4, 5]] + * arrChunk([1, 2, 3], 1); // [[1], [2], [3]] + * arrChunk([1, 2, 3], 5); // [[1, 2, 3]] + * arrChunk([], 2); // [] + * + * // Array-like objects + * arrChunk({ length: 5, 0: "a", 1: "b", 2: "c", 3: "d", 4: "e" }, 2); + * // [["a", "b"], ["c", "d"], ["e"]] + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function arrChunk(theArray: ArrayLike | null | undefined, size: number): T[][] { + const result: T[][] = []; + + if (isArrayLike(theArray) && size > 0) { + let idx = 0; + let chunkIdx = -1; + + arrForEach(theArray, (item) => { + if (idx % size === 0) { + result.push([]); + chunkIdx++; + } + + result[chunkIdx].push(item); + idx++; + }); + } + + return result; +} diff --git a/lib/src/array/compact.ts b/lib/src/array/compact.ts new file mode 100644 index 00000000..a097b48b --- /dev/null +++ b/lib/src/array/compact.ts @@ -0,0 +1,46 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { isArrayLike } from "../helpers/base"; +import { arrForEach } from "./forEach"; + +/** + * The arrCompact() method returns a new array with all falsy values removed. + * Falsy values include: false, 0, -0, 0n, "", null, undefined, and NaN. + * @function + * @since 0.14.0 + * @group Array + * @group ArrayLike + * @typeParam T - Identifies the base type of array elements + * @param theArray - The array or array-like object to compact + * @returns A new array with all falsy values filtered out + * @example + * ```ts + * arrCompact([0, 1, false, 2, "", 3, null, undefined, 4]); // [1, 2, 3, 4] + * arrCompact([false, 0, "", null, undefined]); // [] + * arrCompact([1, 2, 3]); // [1, 2, 3] + * arrCompact([]); // [] + * + * // Array-like objects + * arrCompact({ length: 5, 0: 0, 1: 1, 2: false, 3: 2, 4: null }); // [1, 2] + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function arrCompact(theArray: ArrayLike | null | undefined): T[] { + const result: T[] = []; + + if (isArrayLike(theArray)) { + arrForEach(theArray, (item) => { + if (item) { + result.push(item); + } + }); + } + + return result; +} diff --git a/lib/src/array/difference.ts b/lib/src/array/difference.ts new file mode 100644 index 00000000..9d702503 --- /dev/null +++ b/lib/src/array/difference.ts @@ -0,0 +1,58 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { isArrayLike } from "../helpers/base"; +import { arrForEach } from "./forEach"; +import { arrIncludes } from "./includes"; + +/** + * The arrDifference() method returns a new array containing elements from the first array + * that do not exist in any of the other provided arrays. Uses strict equality (===) for comparison. + * @since 0.14.0 + * @group Array + * @group ArrayLike + * @typeParam T - Identifies the base type of array elements + * @param theArray - The source array to compare from + * @param excludeArrays - One or more arrays whose values should be excluded + * @returns A new array containing elements only in the source array + * @example + * ```ts + * arrDifference([1, 2, 3, 4], [2, 4]); // [1, 3] + * arrDifference([1, 2, 3], [2], [3]); // [1] + * arrDifference(["a", "b", "c"], ["b"]); // ["a", "c"] + * arrDifference([1, 2, 3], []); // [1, 2, 3] + * arrDifference([], [1, 2]); // [] + * + * // Array-like objects + * arrDifference({ length: 3, 0: 1, 1: 2, 2: 3 }, [2]); // [1, 3] + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function arrDifference(theArray: ArrayLike | null | undefined, ...excludeArrays: ArrayLike[]): T[] { + let result: T[] = []; + + if (isArrayLike(theArray)) { + arrForEach(theArray, (item) => { + let excluded = false; + for (let lp = 0; lp < excludeArrays.length; lp++) { + let exclValue = excludeArrays[lp]; + // Check if excludeArray is valid before using arrIncludes + if (isArrayLike(exclValue) && arrIncludes(exclValue, item)) { + excluded = true; + break; + } + } + + if (!excluded) { + result.push(item); + } + }); + } + + return result; +} diff --git a/lib/src/array/drop.ts b/lib/src/array/drop.ts new file mode 100644 index 00000000..ecd7f1c3 --- /dev/null +++ b/lib/src/array/drop.ts @@ -0,0 +1,37 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { arrSlice } from "./slice"; +import { mathMax } from "../math/min_max"; + +/** + * The arrDrop() method returns a new array with the first n elements removed from the source array. + * If n is greater than the array length, returns empty array. If n is negative or 0, returns all elements. + * @since 0.14.0 + * @group Array + * @group ArrayLike + * @typeParam T - Identifies the base type of array elements + * @param theArray - The array or array-like object to drop from + * @param count - The number of elements to drop from the beginning + * @returns A new array with the first n elements removed + * @example + * ```ts + * arrDrop([1, 2, 3, 4, 5], 2); // [3, 4, 5] + * arrDrop(["a", "b", "c"], 1); // ["b", "c"] + * arrDrop([1, 2], 5); // [] + * arrDrop([1, 2, 3], 0); // [1, 2, 3] + * arrDrop([1, 2, 3], -1); // [1, 2, 3] + * + * // Array-like objects + * arrDrop({ length: 4, 0: 1, 1: 2, 2: 3, 3: 4 }, 2); // [3, 4] + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function arrDrop(theArray: ArrayLike | null | undefined, count: number): T[] { + return arrSlice(theArray, mathMax(0, count)); +} diff --git a/lib/src/array/drop_while.ts b/lib/src/array/drop_while.ts new file mode 100644 index 00000000..8155e3b7 --- /dev/null +++ b/lib/src/array/drop_while.ts @@ -0,0 +1,58 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { fnCall } from "../funcs/funcs"; +import { isArrayLike } from "../helpers/base"; +import { getLength } from "../helpers/length"; +import { ArrPredicateCallbackFn, ArrPredicateCallbackFn2 } from "./callbacks"; +import { arrForEach } from "./forEach"; +import { arrSlice } from "./slice"; + +/** + * The arrDropWhile() method returns a new array with elements from the beginning removed + * as long as the predicate function returns true. Once the predicate returns false, + * all remaining elements (including that one) are included in the result. + * @since 0.14.0 + * @group Array + * @group ArrayLike + * @typeParam T - Identifies the base type of array elements + * @typeParam E - Identifies the narrowed type when using type guards + * @param theArray - The array or array-like object to drop from + * @param callbackFn - Function to test each element. Return true to drop, false to stop dropping + * @param thisArg - Optional value to use as `this` when executing callbackFn + * @returns A new array with leading elements removed while predicate was true + * @example + * ```ts + * arrDropWhile([1, 2, 3, 4, 1], x => x < 3); // [3, 4, 1] + * arrDropWhile([2, 4, 6, 1, 8], x => x % 2 === 0); // [1, 8] + * arrDropWhile([1, 2, 3], x => x > 5); // [1, 2, 3] + * arrDropWhile([1, 2, 3], x => x < 5); // [] + * + * // Array-like objects + * arrDropWhile({ length: 4, 0: 1, 1: 2, 2: 3, 3: 4 }, x => x < 3); // [3, 4] + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function arrDropWhile( + theArray: ArrayLike | null | undefined, + callbackFn: ArrPredicateCallbackFn | ArrPredicateCallbackFn2, + thisArg?: any +): T[] { + let result: T[]; + + if (isArrayLike(theArray)) { + arrForEach(theArray, (item, index) => { + if (!fnCall(callbackFn, thisArg, item, index, theArray as any)) { + result = arrSlice(theArray, index); + return -1; // Break out of forEach + } + }); + } + + return result || []; +} diff --git a/lib/src/array/fill.ts b/lib/src/array/fill.ts new file mode 100644 index 00000000..5243f605 --- /dev/null +++ b/lib/src/array/fill.ts @@ -0,0 +1,81 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { ArrProto, UNDEF_VALUE, UNDEFINED } from "../internal/constants"; +import { _unwrapFunctionWithPoly } from "../internal/unwrapFunction"; +import { isArrayLike } from "../helpers/base"; +import { getLength } from "../helpers/length"; +import { WritableArrayLike } from "../helpers/arrayLike"; +import { mathMax, mathMin } from "../math/min_max"; + +/** + * The arrFill() method changes all elements in an array to a static value, from a start index + * (default 0) to an end index (default array.length). It returns the modified array. + * This method mutates the array. + * @function + * @since 0.14.0 + * @group Array + * @typeParam T - Identifies the type of array elements + * @param theArray - The array to fill + * @param value - Value to fill the array with + * @param start - Start index (inclusive), defaults to 0. Negative index counts from the end + * @param end - End index (exclusive), defaults to array.length. Negative index counts from the end + * @returns The modified array + * @example + * ```ts + * arrFill([1, 2, 3], 0); // [0, 0, 0] + * arrFill([1, 2, 3], 4, 1); // [1, 4, 4] + * arrFill([1, 2, 3], 5, 1, 2); // [1, 5, 3] + * arrFill([1, 2, 3], 6, -2); // [1, 6, 6] + * arrFill([1, 2, 3], 7, -3, -1); // [7, 7, 3] + * arrFill([], 1); // [] + * ``` + */ +export const arrFill = (/*#__PURE__*/_unwrapFunctionWithPoly("fill", ArrProto, polyArrFill) as (theArray: WritableArrayLike, value: T, start?: number, end?: number) => WritableArrayLike); + +/** + * Polyfill implementation of Array.fill() for environments that don't support it. + * @function + * @since 0.14.0 + * @group Array + * @group Polyfill + * @typeParam T - Identifies the type of array elements + * @param theArray - The array to fill + * @param value - Value to fill the array with + * @param start - Start index (inclusive), defaults to 0 + * @param end - End index (exclusive), defaults to array.length + * @returns The modified array + */ +/*#__NO_SIDE_EFFECTS__*/ +export function polyArrFill(theArray: WritableArrayLike, value: T, start?: number, end?: number): WritableArrayLike { + if (isArrayLike(theArray)) { + const len = getLength(theArray); + let startIdx = start === UNDEF_VALUE ? 0 : start; + let endIdx = end === UNDEF_VALUE ? len : end; + + // Handle negative indices + if (startIdx < 0) { + startIdx = mathMax(len + startIdx, 0); + } else { + startIdx = mathMin(startIdx, len); + } + + if (endIdx < 0) { + endIdx = mathMax(len + endIdx, 0); + } else { + endIdx = mathMin(endIdx, len); + } + + // Fill the array + for (let i = startIdx; i < endIdx; i++) { + theArray[i] = value; + } + } + + return theArray; +} diff --git a/lib/src/array/flatten.ts b/lib/src/array/flatten.ts new file mode 100644 index 00000000..1f513ccc --- /dev/null +++ b/lib/src/array/flatten.ts @@ -0,0 +1,69 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { isArray, isArrayLike, isUndefined } from "../helpers/base"; +import { arrForEach } from "./forEach"; + +function _addItems(result: any[], arr: any, d: number): void { + const arrLen = arr.length; + let arrIdx = 0; + + while (arrIdx < arrLen) { + const item = arr[arrIdx]; + if (d > 0 && isArray(item)) { + _addItems(result, item, d - 1); + } else { + result.push(item); + } + + arrIdx++; + } +} + + +/** + * The arrFlatten() method returns a new array with all sub-array elements flattened + * up to the specified depth (default 1). + * @function + * @since 0.14.0 + * @group Array + * @group ArrayLike + * @typeParam T - Identifies the base type of array elements + * @param theArray - The array or array-like object to flatten + * @param depth - The flattening depth, defaults to 1. Use Infinity for complete flattening + * @returns A new flattened array + * @example + * ```ts + * arrFlatten([1, [2, 3], [4, [5, 6]]]); // [1, 2, 3, 4, [5, 6]] + * arrFlatten([1, [2, 3], [4, [5, 6]]], 2); // [1, 2, 3, 4, 5, 6] + * arrFlatten([1, [2, 3], [4, [5, 6]]], Infinity); // [1, 2, 3, 4, 5, 6] + * arrFlatten([1, 2, 3]); // [1, 2, 3] + * arrFlatten([]); // [] + * + * // With array-like objects + * arrFlatten({ length: 2, 0: 1, 1: [2, 3] }); // [1, 2, 3] + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function arrFlatten(theArray: ArrayLike | null | undefined, depth?: number): any[] { + const result: any[] = []; + + if (isArrayLike(theArray)) { + const d = isUndefined(depth) ? 1 : depth; + + arrForEach(theArray, (item) => { + if (d > 0 && isArray(item)) { + _addItems(result, item, d - 1); + } else { + result.push(item); + } + }); + } + + return result; +} diff --git a/lib/src/array/groupBy.ts b/lib/src/array/groupBy.ts new file mode 100644 index 00000000..d03d8711 --- /dev/null +++ b/lib/src/array/groupBy.ts @@ -0,0 +1,70 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { isArrayLike, isFunction } from "../helpers/base"; +import { objHasOwn } from "../object/has_own"; +import { asString } from "../string/as_string"; +import { isSymbol } from "../symbol/symbol"; +import { arrForEach } from "./forEach"; + +/** + * Callback function type for arrGroupBy + * @typeParam T - Identifies the base type of array elements + */ +export type ArrGroupByCallbackFn = (value: T, index: number, array: ArrayLike) => string | number | symbol; + +/** + * The arrGroupBy() method groups array elements by the result of a callback function, + * returning an object where keys are group identifiers and values are arrays of grouped elements. + * @function + * @since 0.14.0 + * @group Array + * @group ArrayLike + * @typeParam T - Identifies the base type of array elements + * @param theArray - The array or array-like object to group + * @param callbackFn - Function that determines the group key for each element + * @param thisArg - The value to use as 'this' when executing callbackFn + * @returns An object with group keys as properties and arrays of grouped elements as values + * @example + * ```ts + * const numbers = [1, 2, 3, 4, 5, 6]; + * const grouped = arrGroupBy(numbers, (n) => n % 2 === 0 ? "even" : "odd"); + * // { odd: [1, 3, 5], even: [2, 4, 6] } + * + * const people = [ + * { name: "Alice", age: 30 }, + * { name: "Bob", age: 25 }, + * { name: "Charlie", age: 30 } + * ]; + * const byAge = arrGroupBy(people, (p) => p.age); + * // { "25": [{ name: "Bob", age: 25 }], "30": [{ name: "Alice", age: 30 }, { name: "Charlie", age: 30 }] } + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function arrGroupBy( + theArray: ArrayLike | null | undefined, + callbackFn: ArrGroupByCallbackFn, + thisArg?: any +): Record { + const result: Record = {}; + + if (isArrayLike(theArray) && isFunction(callbackFn)) { + arrForEach(theArray, (item, idx) => { + const keyVal = callbackFn.call(thisArg, item, idx, theArray); + const theKey = isSymbol(keyVal) ? keyVal : asString(keyVal); + + if (!objHasOwn(result, theKey)) { + result[theKey] = []; + } + + result[theKey].push(item); + }); + } + + return result; +} diff --git a/lib/src/array/intersection.ts b/lib/src/array/intersection.ts new file mode 100644 index 00000000..4ea42a87 --- /dev/null +++ b/lib/src/array/intersection.ts @@ -0,0 +1,61 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { isArrayLike } from "../helpers/base"; +import { arrForEach } from "./forEach"; +import { arrIncludes } from "./includes"; + +/** + * The arrIntersection() method returns a new array containing elements that exist in all provided arrays. + * Uses strict equality (===) for comparison and maintains order from the first array. + * @since 0.14.0 + * @group Array + * @group ArrayLike + * @typeParam T - Identifies the base type of array elements + * @param arrays - Two or more arrays to intersect + * @returns A new array containing elements common to all arrays + * @example + * ```ts + * arrIntersection([1, 2, 3], [2, 3, 4]); // [2, 3] + * arrIntersection([1, 2, 3], [2, 3], [3, 4]); // [3] + * arrIntersection(["a", "b"], ["b", "c"]); // ["b"] + * arrIntersection([1, 2], [3, 4]); // [] + * arrIntersection([1, 2, 3], []); // [] + * + * // Array-like objects + * arrIntersection({ length: 3, 0: 1, 1: 2, 2: 3 }, [2, 3]); // [2, 3] + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function arrIntersection(...arrays: ArrayLike[]): T[]; +export function arrIntersection(): T[] { + const result: T[] = []; + let theArrays = arguments; + + if (theArrays.length > 0) { + + const firstArray = theArrays[0]; + if (isArrayLike(firstArray)) { + arrForEach(firstArray, (item) => { + let inAll = true; + arrForEach(theArrays, (arr, index) => { + if (index > 0 && !arrIncludes(arr, item)) { + inAll = false; + return -1; // Break out of forEach + } + }); + + if (inAll && !arrIncludes(result, item)) { + result.push(item); + } + }); + } + } + + return result; +} diff --git a/lib/src/array/partition.ts b/lib/src/array/partition.ts new file mode 100644 index 00000000..310df278 --- /dev/null +++ b/lib/src/array/partition.ts @@ -0,0 +1,59 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { isArrayLike } from "../helpers/base"; +import { ArrPredicateCallbackFn, ArrPredicateCallbackFn2 } from "./callbacks"; +import { arrForEach } from "./forEach"; + +/** + * The arrPartition() method splits an array into two arrays based on a predicate function. + * The first array contains elements for which the predicate returns true, the second contains the rest. + * @since 0.14.0 + * @group Array + * @group ArrayLike + * @typeParam T - Identifies the base type of array elements + * @typeParam E - Identifies the narrowed type when using type guards + * @param theArray - The array or array-like object to partition + * @param callbackFn - Function to test each element. Return true to include in first array, false for second + * @param thisArg - Optional value to use as `this` when executing callbackFn + * @returns A tuple of two arrays: [matching elements, non-matching elements] + * @example + * ```ts + * arrPartition([1, 2, 3, 4, 5], x => x % 2 === 0); // [[2, 4], [1, 3, 5]] + * arrPartition(["a", "bb", "ccc"], x => x.length > 1); // [["bb", "ccc"], ["a"]] + * + * // With type guard + * const mixed: (string | number)[] = [1, "a", 2, "b"]; + * const [strings, numbers] = arrPartition(mixed, (x): x is string => typeof x === "string"); + * // strings: string[], numbers: (string | number)[] + * + * // Array-like objects + * arrPartition({ length: 4, 0: 1, 1: 2, 2: 3, 3: 4 }, x => x > 2); // [[3, 4], [1, 2]] + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function arrPartition( + theArray: ArrayLike | null | undefined, + callbackFn: ArrPredicateCallbackFn | ArrPredicateCallbackFn2, + thisArg?: any +): [T[] | E[], T[]] { + const truthy: T[] = []; + const falsy: T[] = []; + + if (isArrayLike(theArray)) { + arrForEach(theArray, (value, index, array) => { + if (callbackFn.call(thisArg, value, index, array)) { + truthy.push(value); + } else { + falsy.push(value); + } + }); + } + + return [truthy as any, falsy]; +} diff --git a/lib/src/array/reverse.ts b/lib/src/array/reverse.ts new file mode 100644 index 00000000..c9121ff7 --- /dev/null +++ b/lib/src/array/reverse.ts @@ -0,0 +1,38 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { WritableArrayLike } from "../helpers/arrayLike"; +import { ArrProto } from "../internal/constants"; +import { _unwrapFunction } from "../internal/unwrapFunction"; + +/** + * The arrReverse() method reverses an array in place and returns the reference to the same array. + * The first array element becomes the last, and the last array element becomes the first. + * If you want to reverse an array without modifying the original, use arrSlice() first. + * @function + * @since 0.14.0 + * @group Array + * @param theArray - The array to reverse + * @returns The reversed array (same reference as input) + * @example + * ```ts + * const arr1 = [1, 2, 3]; + * arrReverse(arr1); // [3, 2, 1] + * // arr1 is now [3, 2, 1] + * + * arrReverse(["a", "b", "c"]); // ["c", "b", "a"] + * arrReverse([1]); // [1] + * arrReverse([]); // [] + * + * // Non-mutating usage + * const original = [1, 2, 3]; + * const reversed = arrReverse(arrSlice(original)); + * // original: [1, 2, 3], reversed: [3, 2, 1] + * ``` + */ +export const arrReverse: (theArray: WritableArrayLike) => WritableArrayLike = (/*#__PURE__*/_unwrapFunction("reverse", ArrProto)); diff --git a/lib/src/array/rotate.ts b/lib/src/array/rotate.ts new file mode 100644 index 00000000..de72b0a0 --- /dev/null +++ b/lib/src/array/rotate.ts @@ -0,0 +1,69 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { arrSlice } from "./slice"; +import { getLength } from "../helpers/length"; +import { isArrayLike } from "../helpers/base"; +import { arrAppend } from "./append"; + +/** + * The arrRotate() method returns a new array with elements rotated by the specified number of positions. + * Positive values rotate left (elements shift forward), negative values rotate right + * (elements shift backward). The original array is not modified. + * @since 0.14.0 + * @group Array + * @group ArrayLike + * @typeParam T - Identifies the type of array elements + * @param theArray - The array or array-like object to rotate + * @param count - Number of positions to rotate. Positive = right, negative = left + * @returns A new array with elements rotated + * @example + * ```ts + * arrRotate([1, 2, 3, 4, 5], 2); // [3, 4, 5, 1, 2] + * arrRotate([1, 2, 3, 4, 5], -2); // [4, 5, 1, 2, 3] + * arrRotate([1, 2, 3], 0); // [1, 2, 3] + * arrRotate([1, 2, 3], 3); // [1, 2, 3] (full rotation) + * arrRotate([1, 2, 3], 5); // [3, 1, 2] (wraps around) + * arrRotate([1, 2, 3], -5); // [2, 3, 1] (wraps around) + * arrRotate([], 3); // [] + * + * // Array-like objects + * arrRotate({ length: 3, 0: "a", 1: "b", 2: "c" }, 1); // ["b", "c", "a"] + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function arrRotate(theArray: ArrayLike | null | undefined, count: number): T[] { + let result: T[]; + + if (isArrayLike(theArray)) { + const len = theArray.length; + + if (!(len === 0 || count === 0)) { + // Normalize the rotation count to be within array bounds + let rotations = count % len; + if (rotations < 0) { + rotations = len + rotations; + } + + // If no effective rotation needed + if (rotations !== 0) { + // Split at rotation point and recombine + // For positive count: rotate left (elements shift forward) + result = []; + arrAppend(result, arrSlice(theArray, rotations)); + arrAppend(result, arrSlice(theArray, 0, rotations)); + } + } + + if (!result) { + result = arrSlice(theArray); + } + } + + return result || []; +} diff --git a/lib/src/array/sample.ts b/lib/src/array/sample.ts new file mode 100644 index 00000000..5a73d8b1 --- /dev/null +++ b/lib/src/array/sample.ts @@ -0,0 +1,48 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { isArrayLike } from "../helpers/base"; +import { mathFloor } from "../math/floor"; +import { mathRandom } from "../math/random"; +import { getLength } from "../helpers/length"; + +/** + * The arrSample() method returns a random element from the array. + * Returns undefined if the array is empty or null/undefined. + * @since 0.14.0 + * @group Array + * @group ArrayLike + * @typeParam T - Identifies the base type of array elements + * @param theArray - The array or array-like object to sample from + * @returns A random element from the array, or undefined if empty + * @example + * ```ts + * arrSample([1, 2, 3, 4, 5]); // e.g., 3 + * arrSample(["a", "b", "c"]); // e.g., "b" + * arrSample([42]); // 42 + * arrSample([]); // undefined + * arrSample(null); // undefined + * + * // Array-like objects + * arrSample({ length: 3, 0: "x", 1: "y", 2: "z" }); // e.g., "y" + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function arrSample(theArray: ArrayLike | null | undefined): T | undefined { + let result: T | undefined; + + if (isArrayLike(theArray)) { + const len = getLength(theArray); + if (len !== 0) { + const index = mathFloor(mathRandom() * len); + result = theArray[index]; + } + } + + return result; +} diff --git a/lib/src/array/shuffle.ts b/lib/src/array/shuffle.ts new file mode 100644 index 00000000..e6901ad5 --- /dev/null +++ b/lib/src/array/shuffle.ts @@ -0,0 +1,48 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { mathFloor } from "../math/floor"; +import { mathRandom } from "../math/random"; +import { getLength } from "../helpers/length"; +import { arrSlice } from "./slice"; + +/** + * The arrShuffle() method returns a new array with elements randomly shuffled. + * Uses the Fisher-Yates shuffle algorithm for uniform randomization. + * @since 0.14.0 + * @group Array + * @group ArrayLike + * @typeParam T - Identifies the base type of array elements + * @param theArray - The array or array-like object to shuffle + * @returns A new array with elements in random order + * @example + * ```ts + * arrShuffle([1, 2, 3, 4, 5]); // e.g., [3, 1, 5, 2, 4] + * arrShuffle(["a", "b", "c"]); // e.g., ["c", "a", "b"] + * arrShuffle([1]); // [1] + * arrShuffle([]); // [] + * + * // Array-like objects + * arrShuffle({ length: 3, 0: "x", 1: "y", 2: "z" }); // e.g., ["z", "x", "y"] + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function arrShuffle(theArray: ArrayLike | null | undefined): T[] { + const result = arrSlice(theArray); + const len = getLength(result); + + // Fisher-Yates shuffle + for (let i = len - 1; i > 0; i--) { + const swapVal = result[i]; + const j = mathFloor(mathRandom() * (i + 1)); + result[i] = result[j]; + result[j] = swapVal; + } + + return result; +} diff --git a/lib/src/array/take.ts b/lib/src/array/take.ts new file mode 100644 index 00000000..1c7cfe76 --- /dev/null +++ b/lib/src/array/take.ts @@ -0,0 +1,37 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { arrSlice } from "./slice"; +import { mathMax } from "../math/min_max"; + +/** + * The arrTake() method returns a new array with the first n elements from the source array. + * If n is greater than the array length, returns all elements. If n is negative or 0, returns empty array. + * @since 0.14.0 + * @group Array + * @group ArrayLike + * @typeParam T - Identifies the base type of array elements + * @param theArray - The array or array-like object to take from + * @param count - The number of elements to take from the beginning + * @returns A new array containing the first n elements + * @example + * ```ts + * arrTake([1, 2, 3, 4, 5], 3); // [1, 2, 3] + * arrTake(["a", "b", "c"], 2); // ["a", "b"] + * arrTake([1, 2], 5); // [1, 2] + * arrTake([1, 2, 3], 0); // [] + * arrTake([1, 2, 3], -1); // [] + * + * // Array-like objects + * arrTake({ length: 4, 0: 1, 1: 2, 2: 3, 3: 4 }, 2); // [1, 2] + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function arrTake(theArray: ArrayLike | null | undefined, count: number): T[] { + return arrSlice(theArray, 0, mathMax(0, count)); +} diff --git a/lib/src/array/take_while.ts b/lib/src/array/take_while.ts new file mode 100644 index 00000000..8e407132 --- /dev/null +++ b/lib/src/array/take_while.ts @@ -0,0 +1,56 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { isArrayLike } from "../helpers/base"; +import { getLength } from "../helpers/length"; +import { ArrPredicateCallbackFn, ArrPredicateCallbackFn2 } from "./callbacks"; +import { arrForEach } from "./forEach"; + +/** + * The arrTakeWhile() method returns a new array containing elements from the beginning of the array + * as long as the predicate function returns true. Once the predicate returns false, iteration stops. + * @since 0.14.0 + * @group Array + * @group ArrayLike + * @typeParam T - Identifies the base type of array elements + * @typeParam E - Identifies the narrowed type when using type guards + * @param theArray - The array or array-like object to take from + * @param callbackFn - Function to test each element. Return true to continue, false to stop + * @param thisArg - Optional value to use as `this` when executing callbackFn + * @returns A new array containing elements until the predicate returns false + * @example + * ```ts + * arrTakeWhile([1, 2, 3, 4, 1], x => x < 3); // [1, 2] + * arrTakeWhile([2, 4, 6, 1, 8], x => x % 2 === 0); // [2, 4, 6] + * arrTakeWhile([1, 2, 3], x => x > 5); // [] + * arrTakeWhile([1, 2, 3], x => x < 5); // [1, 2, 3] + * + * // Array-like objects + * arrTakeWhile({ length: 4, 0: 1, 1: 2, 2: 3, 3: 4 }, x => x < 3); // [1, 2] + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function arrTakeWhile( + theArray: ArrayLike | null | undefined, + callbackFn: ArrPredicateCallbackFn | ArrPredicateCallbackFn2, + thisArg?: any +): T[] | E[] { + const result: T[] = []; + + if (isArrayLike(theArray)) { + arrForEach(theArray, (value, idx) => { + if (!callbackFn.call(thisArg, value, idx, theArray as any)) { + return -1; + } + + result.push(value); + }); + } + + return result as (T[] | E[]); +} diff --git a/lib/src/array/union.ts b/lib/src/array/union.ts new file mode 100644 index 00000000..42b85fd1 --- /dev/null +++ b/lib/src/array/union.ts @@ -0,0 +1,46 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { arrUnique } from "./unique"; +import { arrAppend } from "./append"; +import { arrSlice } from "./slice"; +import { arrForEach } from "./forEach"; + +/** + * The arrUnion() method returns a new array containing all unique elements from all provided arrays. + * Uses strict equality (===) for comparison and maintains insertion order. + * @since 0.14.0 + * @group Array + * @group ArrayLike + * @typeParam T - Identifies the base type of array elements + * @param arrays - One or more arrays to combine + * @returns A new array containing all unique elements from all arrays + * @example + * ```ts + * arrUnion([1, 2], [2, 3]); // [1, 2, 3] + * arrUnion([1, 2], [3, 4], [4, 5]); // [1, 2, 3, 4, 5] + * arrUnion(["a", "b"], ["b", "c"]); // ["a", "b", "c"] + * arrUnion([1, 1, 2], [2, 2, 3]); // [1, 2, 3] + * arrUnion([], [1, 2]); // [1, 2] + * + * // Array-like objects + * arrUnion({ length: 2, 0: 1, 1: 2 }, [2, 3]); // [1, 2, 3] + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function arrUnion(...arrays: ArrayLike[]): T[]; +export function arrUnion(): T[] { + let combined: T[] = []; + let theArgs = arguments; + + arrForEach(theArgs, (arr) => { + combined = arrAppend(combined, arrSlice(arr)); + }); + + return arrUnique(combined); +} diff --git a/lib/src/array/unique.ts b/lib/src/array/unique.ts new file mode 100644 index 00000000..ff6f8a7a --- /dev/null +++ b/lib/src/array/unique.ts @@ -0,0 +1,55 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { isArrayLike } from "../helpers/base"; +import { normalizeJsName } from "../helpers/encode"; +import { objCreate } from "../object/create"; +import { objHasOwn } from "../object/has_own"; +import { arrForEach } from "./forEach"; + +/** + * The arrUnique() method returns a new array with duplicate elements removed. + * Uses strict equality (===) for comparison and maintains insertion order. + * @function + * @since 0.14.0 + * @group Array + * @group ArrayLike + * @typeParam T - Identifies the base type of array elements + * @param theArray - The array or array-like object to remove duplicates from + * @returns A new array with duplicate values removed, preserving order of first occurrence + * @example + * ```ts + * arrUnique([1, 2, 2, 3, 1, 4]); // [1, 2, 3, 4] + * arrUnique(["a", "b", "a", "c"]); // ["a", "b", "c"] + * arrUnique([1, "1", 1, "1"]); // [1, "1"] + * arrUnique([]); // [] + * arrUnique([1]); // [1] + * + * // Array-like objects + * arrUnique({ length: 4, 0: 1, 1: 2, 2: 2, 3: 3 }); // [1, 2, 3] + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function arrUnique(theArray: ArrayLike | null | undefined): T[] { + const result: T[] = []; + + if (isArrayLike(theArray)) { + const seen: any = objCreate(null); + + arrForEach(theArray, (item) => { + const key = ((typeof item) + "_" + item); + + if (!objHasOwn(seen, key)) { + seen[key] = 1; + result.push(item); + } + }); + } + + return result; +} diff --git a/lib/src/array/unzip.ts b/lib/src/array/unzip.ts new file mode 100644 index 00000000..608af109 --- /dev/null +++ b/lib/src/array/unzip.ts @@ -0,0 +1,76 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { isArrayLike } from "../helpers/base"; +import { getLength } from "../helpers/length"; +import { UNDEF_VALUE } from "../internal/constants"; +import { arrForEach } from "./forEach"; + +/** + * The arrUnzip() method reverses the operation of arrZip(). Given an array of grouped elements, + * it creates multiple arrays where each contains elements from the same position in each group. + * This is the inverse operation of arrZip(). + * @since 0.14.0 + * @group Array + * @group ArrayLike + * @param theArray - An array of arrays to unzip + * @returns An array of arrays, ungrouped by position + * @example + * ```ts + * arrUnzip([[1, "a"], [2, "b"], [3, "c"]]); // [[1, 2, 3], ["a", "b", "c"]] + * arrUnzip([[1, "a", true], [2, "b", false]]); // [[1, 2], ["a", "b"], [true, false]] + * arrUnzip([[1], [2], [3]]); // [[1, 2, 3]] + * arrUnzip([]); // [] + * + * // Array-like objects + * arrUnzip({ length: 2, 0: [1, "x"], 1: [2, "y"] }); // [[1, 2], ["x", "y"]] + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function arrUnzip(theArray: ArrayLike> | null | undefined): any[][] { + let result: any[][]; + + if (isArrayLike(theArray)) { + const len = getLength(theArray); + + if (len !== 0) { + // Find the maximum length among all sub-arrays + let maxLen = 0; + + arrForEach(theArray, (subArray) => { + if (isArrayLike(subArray)) { + const subLen = getLength(subArray); + + if (subLen > maxLen) { + maxLen = subLen; + } + } + }); + + if (maxLen !== 0) { + // Create result arrays + result = []; + + for (let lp = 0; lp < maxLen; lp++) { + result.push([]); + } + + // Fill result arrays + arrForEach(theArray, (subArray) => { + if (isArrayLike(subArray)) { + for (let lp = 0; lp < maxLen; lp++) { + result[lp].push(lp < getLength(subArray) ? subArray[lp] : UNDEF_VALUE); + } + } + }); + } + } + } + + return result || []; +} diff --git a/lib/src/array/with.ts b/lib/src/array/with.ts new file mode 100644 index 00000000..7e93a709 --- /dev/null +++ b/lib/src/array/with.ts @@ -0,0 +1,84 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { ArrProto } from "../internal/constants"; +import { _unwrapFunctionWithPoly } from "../internal/unwrapFunction"; +import { isArrayLike } from "../helpers/base"; +import { getLength } from "../helpers/length"; +import { arrSlice } from "./slice"; + +/** + * The arrWith() method is the copying version of using the bracket notation to change the value + * of a given index. It returns a new array with the element at the given index replaced with the + * given value. This is an ES2023 feature with polyfill support for older environments. + * @function + * @since 0.14.0 + * @group Array + * @group ArrayLike + * @typeParam T - Identifies the type of array elements + * @param theArray - The array or array-like object to copy from + * @param index - The index at which to change the value. Negative index counts from the end + * @param value - The new value to set at the index + * @returns A new array with the element at index replaced, or the original array if index is out of range + * @example + * ```ts + * arrWith([1, 2, 3], 1, 5); // [1, 5, 3] + * arrWith([1, 2, 3], -1, 5); // [1, 2, 5] + * arrWith([1, 2, 3], -2, 5); // [1, 5, 3] + * arrWith([1, 2, 3], 10, 5); // RangeError (out of bounds) + * + * const original = [1, 2, 3]; + * const modified = arrWith(original, 1, 9); + * // original: [1, 2, 3], modified: [1, 9, 3] + * + * // Array-like objects + * arrWith({ length: 3, 0: "a", 1: "b", 2: "c" }, 1, "x"); // ["a", "x", "c"] + * ``` + */ +export const arrWith = (/*#__PURE__*/_unwrapFunctionWithPoly("with", ArrProto as any, polyArrWith) as (theArray: ArrayLike, index: number, value: T) => T[]); + +/** + * Polyfill implementation of Array.with() for environments that don't support it. + * @since 0.14.0 + * @group Array + * @group ArrayLike + * @group Polyfill + * @typeParam T - Identifies the type of array elements + * @param theArray - The array or array-like object to copy from + * @param index - The index at which to change the value + * @param value - The new value to set at the index + * @returns A new array with the element at index replaced + * @throws RangeError if the index is out of bounds + */ +/*#__NO_SIDE_EFFECTS__*/ +export function polyArrWith(theArray: ArrayLike, index: number, value: T): T[] { + let result: T[]; + + if (!isArrayLike(theArray)) { + throw new RangeError("Invalid array"); + } + + const len = getLength(theArray); + let idx = index; + + // Convert negative index to positive + if (idx < 0) { + idx = len + idx; + } + + // Check bounds + if (idx < 0 || idx >= len) { + throw new RangeError("Index out of bounds"); + } + + // Create a copy and set value + result = arrSlice(theArray); + result[idx] = value; + + return result; +} diff --git a/lib/src/array/zip.ts b/lib/src/array/zip.ts new file mode 100644 index 00000000..72c14e58 --- /dev/null +++ b/lib/src/array/zip.ts @@ -0,0 +1,72 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { isArrayLike } from "../helpers/base"; +import { getLength } from "../helpers/length"; +import { mathMin } from "../math/min_max"; + +/** + * The arrZip() method creates a new array of grouped elements, where the first array contains + * the first elements of each input array, the second array contains the second elements, and so on. + * The length of the result is determined by the shortest input array. + * @since 0.14.0 + * @group Array + * @group ArrayLike + * @param arrays - Two or more arrays to zip together + * @returns A new array of arrays, grouped by index + * @example + * ```ts + * arrZip([1, 2, 3], ["a", "b", "c"]); // [[1, "a"], [2, "b"], [3, "c"]] + * arrZip([1, 2], ["a", "b", "c"]); // [[1, "a"], [2, "b"]] + * arrZip([1, 2, 3], ["a", "b"], [true, false]); // [[1, "a", true], [2, "b", false]] + * arrZip([1], []); // [] + * + * // Array-like objects + * arrZip({ length: 2, 0: 1, 1: 2 }, ["x", "y"]); // [[1, "x"], [2, "y"]] + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function arrZip(...arrays: ArrayLike[]): any[][] { + let result: any[][]; + + if (arrays.length !== 0) { + // Filter out null/undefined arrays and find the minimum length + const validArrays: ArrayLike[] = []; + + for (let i = 0; i < arrays.length; i++) { + if (isArrayLike(arrays[i])) { + validArrays.push(arrays[i]); + } + } + + if (validArrays.length !== 0) { + // Find the minimum length among all valid arrays + let minLen = Infinity; + + for (let lp = 0; lp < validArrays.length; lp++) { + minLen = mathMin(minLen, getLength(validArrays[lp])); + } + + if (isFinite(minLen) && minLen !== 0) { + result = []; + + for (let lp = 0; lp < minLen; lp++) { + const group: any[] = []; + + for (let j = 0; j < validArrays.length; j++) { + group.push(validArrays[j][lp]); + } + + result.push(group); + } + } + } + } + + return result || []; +} diff --git a/lib/src/helpers/arrayLike.ts b/lib/src/helpers/arrayLike.ts new file mode 100644 index 00000000..a42f903b --- /dev/null +++ b/lib/src/helpers/arrayLike.ts @@ -0,0 +1,25 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +/** + * Writable (mutable) ArrayLike interface. + * This is similar to the built-in ArrayLike, but with mutable properties. + * @since 0.14.0 + * @group ArrayLike + * @typeParam T - Identifies the base type of array elements + * @example + * ```ts + * const buf: WritableArrayLike = { length: 3, 0: 1, 1: 2, 2: 3 }; + * buf[1] = 42; + * buf.length = 4; + * ``` + */ +export interface WritableArrayLike { + length: number; + [index: number]: T; +} diff --git a/lib/src/helpers/base.ts b/lib/src/helpers/base.ts index 09bca689..f349cbaa 100644 --- a/lib/src/helpers/base.ts +++ b/lib/src/helpers/base.ts @@ -6,7 +6,7 @@ * Licensed under the MIT license. */ -import { ArrCls, FUNCTION, NULL_VALUE, OBJECT, ObjProto, TO_STRING, UNDEFINED, UNDEF_VALUE } from "../internal/constants"; +import { ArrCls, FUNCTION, LENGTH, NULL_VALUE, OBJECT, ObjProto, TO_STRING, UNDEFINED, UNDEF_VALUE } from "../internal/constants"; import { _isPolyfillType } from "../internal/poly_helpers"; import { _pureRef } from "../internal/treeshake_helpers"; import { safeGet } from "./safe_get"; @@ -542,6 +542,32 @@ export function isObject(value: T): value is T { */ export const isArray: (arg: any) => arg is Array = (/* #__PURE__*/_pureRef(ArrCls as any, "isArray")); +/** + * Checks if the type of value is array-like (has a numeric length property and numeric indices). + * @since 0.14.0 + * @function + * @group Type Identity + * @group Array + * @param arg - Value to be checked. + * @return True if the value is array-like, false otherwise. + * @example + * ```ts + * import { isArrayLike } from "@nevware21/ts-utils"; + * + * isArrayLike([1, 2, 3]); // true + * isArrayLike("hello"); // true + * isArrayLike({ length: 3, 0: "a", 1: "b", 2: "c" }); // true + * isArrayLike({ length: "3" }); // false (length is not a number) + * isArrayLike({ 0: "a", 1: "b" }); // false (no length property) + * isArrayLike(null); // false + * isArrayLike(undefined); // false + * ``` + */ +/*#__NO_SIDE_EFFECTS__*/ +export function isArrayLike(arg: any): arg is ArrayLike { + return !isStrictNullOrUndefined(arg) && !isFunction(arg) && isNumber(arg[LENGTH]) && arg[LENGTH] >= 0; +} + /** * Check if an object is of type Date * @function diff --git a/lib/src/index.ts b/lib/src/index.ts index 0c5fb18e..59149f54 100644 --- a/lib/src/index.ts +++ b/lib/src/index.ts @@ -8,22 +8,45 @@ export { arrAppend } from "./array/append"; export { ArrPredicateCallbackFn, ArrPredicateCallbackFn2, ArrMapCallbackFn, ArrFromMapFn } from "./array/callbacks"; +export { arrAt } from "./array/at"; +export { arrChunk } from "./array/chunk"; +export { arrCompact } from "./array/compact"; +export { arrDifference } from "./array/difference"; +export { arrDrop } from "./array/drop"; +export { arrDropWhile } from "./array/drop_while"; export { arrEvery, arrFilter } from "./array/every"; +export { arrFill } from "./array/fill"; export { arrFind, arrFindIndex, arrFindLast, arrFindLastIndex } from "./array/find"; +export { arrFlatten } from "./array/flatten"; export { arrForEach } from "./array/forEach"; export { arrFrom } from "./array/from"; +export { ArrGroupByCallbackFn, arrGroupBy } from "./array/groupBy"; export { arrContains, arrIncludes } from "./array/includes"; export { arrIndexOf, arrLastIndexOf } from "./array/indexOf"; +export { arrIntersection } from "./array/intersection"; export { arrMap } from "./array/map"; +export { arrPartition } from "./array/partition"; export { ArrReduceCallbackFn, arrReduce } from "./array/reduce"; +export { arrReverse } from "./array/reverse"; +export { arrRotate } from "./array/rotate"; +export { arrSample } from "./array/sample"; +export { arrShuffle } from "./array/shuffle"; export { arrSlice } from "./array/slice"; export { arrSome } from "./array/some"; +export { arrTake } from "./array/take"; +export { arrTakeWhile } from "./array/take_while"; +export { arrUnion } from "./array/union"; +export { arrUnique } from "./array/unique"; +export { arrUnzip } from "./array/unzip"; +export { arrWith } from "./array/with"; +export { arrZip } from "./array/zip"; export { fnApply, fnBind, fnCall } from "./funcs/funcs"; export { createFnDeferredProxy, createProxyFuncs } from "./funcs/fnProxy"; export { readArgs } from "./funcs/readArgs"; export { ProxyFunctionDef, TypeFuncNames } from "./funcs/types"; +export { WritableArrayLike } from "./helpers/arrayLike"; export { - isTypeof, isUndefined, isNullOrUndefined, isDefined, isString, isFunction, isObject, isArray, isDate, isNumber, isBoolean, + isTypeof, isUndefined, isNullOrUndefined, isDefined, isString, isFunction, isObject, isArray, isArrayLike, isDate, isNumber, isBoolean, isRegExp, isFile, isFormData, isBlob, isArrayBuffer, isPromiseLike, isPromise, isThenable, isNotTruthy, isTruthy, objToString, isStrictNullOrUndefined, isStrictUndefined, isError, isPrimitive, isPrimitiveType, isMap, isMapLike, isSet, isSetLike, isWeakMap, isWeakSet, isBigInt, isAsyncFunction, isGenerator, isAsyncGenerator diff --git a/lib/src/internal/unwrapFunction.ts b/lib/src/internal/unwrapFunction.ts index 1c2ab8d6..1a09072b 100644 --- a/lib/src/internal/unwrapFunction.ts +++ b/lib/src/internal/unwrapFunction.ts @@ -22,7 +22,7 @@ import { ArrSlice, CALL, NULL_VALUE } from "./constants"; * @param funcName - The function name to call on the first argument passed to the wrapped function * @returns A function which will call the funcName against the first passed argument and pass on the remaining arguments */ -export const _unwrapInstFunction:(funcName: keyof T) => (this: T, ..._args:any) => R = (/*#__PURE__*/_unwrapFunctionWithPoly); +export const _unwrapInstFunction:(funcName: keyof T) => (this: T, ..._args:any) => R = _unwrapFunctionWithPoly; /** * @function @@ -33,7 +33,7 @@ export const _unwrapInstFunction:(funcName: keyof T) => (this: T, ..._a * @param clsProto - The Class or class prototype to fallback to if the instance doesn't have the function. * @returns A function which will call the funcName against the first passed argument and pass on the remaining arguments */ -export const _unwrapFunction:(funcName: keyof T, clsProto: T) => (this: T, ..._args:any) => R = (/*#__PURE__*/_unwrapFunctionWithPoly); +export const _unwrapFunction:(funcName: keyof T, clsProto: T) => (this: T, ..._args:any) => R = _unwrapFunctionWithPoly; /** * @internal diff --git a/lib/src/polyfills.ts b/lib/src/polyfills.ts index 2359270e..512fbeab 100644 --- a/lib/src/polyfills.ts +++ b/lib/src/polyfills.ts @@ -25,6 +25,9 @@ import { polyObjIsExtensible } from "./polyfills/object/objIsExtensible"; import { polyObjIsFrozen } from "./polyfills/object/objIsFrozen"; import { polyObjIsSealed } from "./polyfills/object/objIsSealed"; import { polyObjHasOwn } from "./object/has_own"; +import { polyArrAt } from "./array/at"; +import { polyArrFill } from "./array/fill"; +import { polyArrWith } from "./array/with"; (function () { @@ -64,11 +67,14 @@ import { polyObjHasOwn } from "./object/has_own"; }; const arrayPolyfills = { + "at": polyArrAt, + "fill": polyArrFill, "includes": polyArrIncludes, "find": polyArrFind, "findIndex": polyArrFindIndex, "findLast": polyArrFindLast, - "findLastIndex": polyArrFindLastIndex + "findLastIndex": polyArrFindLastIndex, + "with": polyArrWith }; // Add Object polyfills @@ -97,3 +103,5 @@ import { polyObjHasOwn } from "./object/has_own"; } }); })(); + +export { polyArrAt, polyArrFill, polyArrWith }; diff --git a/lib/test/bundle-size-check.js b/lib/test/bundle-size-check.js index 215987f0..6d5dd6c6 100644 --- a/lib/test/bundle-size-check.js +++ b/lib/test/bundle-size-check.js @@ -7,31 +7,31 @@ const configs = [ { name: "es5-min-full", path: "../bundle/es5/umd/ts-utils.min.js", - limit: 27.5 * 1024, // 27.5 kb in bytes + limit: 30.5 * 1024, // 30.5 kb in bytes compress: false }, { name: "es6-min-full", path: "../bundle/es6/umd/ts-utils.min.js", - limit: 27 * 1024, // 27 kb in bytes + limit: 30 * 1024, // 30 kb in bytes compress: false }, { name: "es5-min-zip", path: "../bundle/es5/umd/ts-utils.min.js", - limit: 11 * 1024, // 11 kb in bytes + limit: 12 * 1024, // 12 kb in bytes compress: true }, { name: "es6-min-zip", path: "../bundle/es6/umd/ts-utils.min.js", - limit: 11 * 1024, // 11 kb in bytes + limit: 12 * 1024, // 12 kb in bytes compress: true }, { name: "es5-min-poly", path: "../bundle/es5/ts-polyfills-utils.min.js", - limit: 8 * 1024, // 8 kb in bytes + limit: 9 * 1024, // 9 kb in bytes compress: false } ]; diff --git a/lib/test/src/common/array/at.test.ts b/lib/test/src/common/array/at.test.ts new file mode 100644 index 00000000..97c6a257 --- /dev/null +++ b/lib/test/src/common/array/at.test.ts @@ -0,0 +1,107 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { assert } from "@nevware21/tripwire-chai"; +import { arrAt, polyArrAt } from "../../../../src/array/at"; + +describe("arrAt", () => { + it("should return element at positive index", () => { + assert.equal(arrAt([1, 2, 3], 0), 1); + assert.equal(arrAt([1, 2, 3], 1), 2); + assert.equal(arrAt([1, 2, 3], 2), 3); + }); + + it("should return element at negative index", () => { + assert.equal(arrAt([1, 2, 3], -1), 3); + assert.equal(arrAt([1, 2, 3], -2), 2); + assert.equal(arrAt([1, 2, 3], -3), 1); + }); + + it("should return undefined for out of bounds index", () => { + assert.isUndefined(arrAt([1, 2, 3], 5)); + assert.isUndefined(arrAt([1, 2, 3], -5)); + }); + + it("should handle empty array", () => { + assert.isUndefined(arrAt([], 0)); + }); + + it("should throw for null and undefined", () => { + // Native .at() throws TypeError for null/undefined + assert.throws(() => arrAt(null, 0)); + assert.throws(() => arrAt(undefined, 0)); + }); + + it("should work with array-like objects", () => { + const arrayLike = { length: 3, 0: "a", 1: "b", 2: "c" }; + assert.equal(arrAt(arrayLike, 0), "a"); + assert.equal(arrAt(arrayLike, -1), "c"); + }); + + it("should work with strings", () => { + assert.equal(arrAt("hello", 0), "h"); + assert.equal(arrAt("hello", -1), "o"); + }); +}); + +describe("polyArrAt", () => { + it("should match native Array.prototype.at behavior for positive indices", () => { + const arr = [1, 2, 3, 4, 5]; + for (let i = 0; i < arr.length; i++) { + assert.equal( + polyArrAt(arr, i), + (arr as any).at ? (arr as any).at(i) : arr[i], + `Index ${i} should match` + ); + } + }); + + it("should match native Array.prototype.at behavior for negative indices", () => { + const arr = [1, 2, 3, 4, 5]; + for (let i = -1; i >= -arr.length; i--) { + const polyResult = polyArrAt(arr, i); + const nativeResult = (arr as any).at ? (arr as any).at(i) : arr[arr.length + i]; + assert.equal(polyResult, nativeResult, `Index ${i} should match`); + } + }); + + it("should return undefined for out of bounds like native", () => { + const arr = [1, 2, 3]; + assert.isUndefined(polyArrAt(arr, 10)); + assert.isUndefined(polyArrAt(arr, -10)); + + if ((arr as any).at) { + assert.equal(polyArrAt(arr, 10), (arr as any).at(10)); + assert.equal(polyArrAt(arr, -10), (arr as any).at(-10)); + } + }); + + it("should handle index 0", () => { + const arr = [42]; + assert.equal(polyArrAt(arr, 0), 42); + if ((arr as any).at) { + assert.equal(polyArrAt(arr, 0), (arr as any).at(0)); + } + }); + + it("should handle empty array like native", () => { + const arr: number[] = []; + assert.isUndefined(polyArrAt(arr, 0)); + if ((arr as any).at) { + assert.equal(polyArrAt(arr, 0), (arr as any).at(0)); + } + }); + + it("should work with array-like objects", () => { + const arrayLike = { length: 3, 0: "x", 1: "y", 2: "z" }; + assert.equal(polyArrAt(arrayLike, 0), "x"); + assert.equal(polyArrAt(arrayLike, -1), "z"); + assert.equal(polyArrAt(arrayLike, 1), "y"); + assert.equal(polyArrAt(arrayLike, -2), "y"); + }); +}); diff --git a/lib/test/src/common/array/difference.test.ts b/lib/test/src/common/array/difference.test.ts new file mode 100644 index 00000000..7cc37e8d --- /dev/null +++ b/lib/test/src/common/array/difference.test.ts @@ -0,0 +1,60 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { assert } from "@nevware21/tripwire-chai"; +import { arrDifference } from "../../../../src/array/difference"; + +describe("arrDifference", () => { + it("should return elements in first array not in second array", () => { + assert.deepEqual(arrDifference([1, 2, 3, 4], [2, 4]), [1, 3]); + assert.deepEqual(arrDifference(["a", "b", "c"], ["b"]), ["a", "c"]); + }); + + it("should handle multiple exclude arrays", () => { + assert.deepEqual(arrDifference([1, 2, 3], [2], [3]), [1]); + assert.deepEqual(arrDifference([1, 2, 3, 4, 5], [2, 4], [3, 5]), [1]); + }); + + it("should return all elements when exclude arrays are empty", () => { + assert.deepEqual(arrDifference([1, 2, 3], []), [1, 2, 3]); + assert.deepEqual(arrDifference([1, 2, 3]), [1, 2, 3]); + }); + + it("should return empty array when source is empty", () => { + assert.deepEqual(arrDifference([], [1, 2]), []); + }); + + it("should handle null source array", () => { + assert.deepEqual(arrDifference(null, [1, 2]), []); + assert.deepEqual(arrDifference(undefined, [1, 2]), []); + }); + + it("should handle null exclude arrays", () => { + assert.deepEqual(arrDifference([1, 2, 3], null), [1, 2, 3]); + assert.deepEqual(arrDifference([1, 2, 3], undefined), [1, 2, 3]); + assert.deepEqual(arrDifference([1, 2, 3], [2], null), [1, 3]); + }); + + it("should work with array-like objects", () => { + const arrayLike = { length: 3, 0: 1, 1: 2, 2: 3 }; + assert.deepEqual(arrDifference(arrayLike, [2]), [1, 3]); + }); + + it("should use strict equality", () => { + assert.deepEqual(arrDifference([1, 2, 3], ["1", "2"]), [1, 2, 3]); + assert.deepEqual(arrDifference(["1", "2", "3"], [1, 2]), ["1", "2", "3"]); + }); + + it("should preserve order from source array", () => { + assert.deepEqual(arrDifference([5, 3, 1, 4, 2], [3, 1]), [5, 4, 2]); + }); + + it("should handle duplicate values in source array", () => { + assert.deepEqual(arrDifference([1, 2, 2, 3, 3, 3], [2]), [1, 3, 3, 3]); + }); +}); diff --git a/lib/test/src/common/array/fill.test.ts b/lib/test/src/common/array/fill.test.ts new file mode 100644 index 00000000..e4c54fb1 --- /dev/null +++ b/lib/test/src/common/array/fill.test.ts @@ -0,0 +1,151 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { assert } from "@nevware21/tripwire-chai"; +import { arrFill, polyArrFill } from "../../../../src/array/fill"; + +describe("arrFill", () => { + it("should fill entire array with value", () => { + const arr = [1, 2, 3, 4]; + const result = arrFill(arr, 0); + + assert.deepEqual(result, [0, 0, 0, 0]); + assert.deepEqual(arr, [0, 0, 0, 0]); // Mutates original + }); + + it("should fill from start index", () => { + const arr = [1, 2, 3, 4]; + arrFill(arr, 0, 2); + + assert.deepEqual(arr, [1, 2, 0, 0]); + }); + + it("should fill between start and end indices", () => { + const arr = [1, 2, 3, 4, 5]; + arrFill(arr, 9, 1, 3); + + assert.deepEqual(arr, [1, 9, 9, 4, 5]); + }); + + it("should handle negative start index", () => { + const arr = [1, 2, 3, 4]; + arrFill(arr, 0, -2); + + assert.deepEqual(arr, [1, 2, 0, 0]); + }); + + it("should handle negative end index", () => { + const arr = [1, 2, 3, 4]; + arrFill(arr, 0, 0, -1); + + assert.deepEqual(arr, [0, 0, 0, 4]); + }); + + it("should throw for null and undefined", () => { + // Native .fill() throws TypeError for null/undefined + assert.throws(() => arrFill(null, 0)); + assert.throws(() => arrFill(undefined, 0)); + }); + + it("should work with array-like objects", () => { + const arrayLike = { length: 3, 0: 1, 1: 2, 2: 3 }; + const result = arrFill(arrayLike, 0); + + assert.equal(result[0], 0); + assert.equal(result[1], 0); + assert.equal(result[2], 0); + }); +}); + +describe("polyArrFill", () => { + it("should match native Array.prototype.fill for full fill", () => { + const arr1 = [1, 2, 3, 4]; + const arr2 = [1, 2, 3, 4]; + + polyArrFill(arr1, 0); + if (arr2.fill) { + arr2.fill(0); + } else { + for (let i = 0; i < arr2.length; i++) { + arr2[i] = 0; + } + } + + assert.deepEqual(arr1, arr2); + }); + + it("should match native Array.prototype.fill with start index", () => { + const arr1 = [1, 2, 3, 4]; + const arr2 = [1, 2, 3, 4]; + + polyArrFill(arr1, 9, 2); + if (arr2.fill) { + arr2.fill(9, 2); + assert.deepEqual(arr1, arr2); + } + }); + + it("should match native Array.prototype.fill with start and end", () => { + const arr1 = [1, 2, 3, 4, 5]; + const arr2 = [1, 2, 3, 4, 5]; + + polyArrFill(arr1, 8, 1, 3); + if (arr2.fill) { + arr2.fill(8, 1, 3); + assert.deepEqual(arr1, arr2); + } + }); + + it("should match native for negative indices", () => { + const arr1 = [1, 2, 3, 4]; + const arr2 = [1, 2, 3, 4]; + + polyArrFill(arr1, 0, -2, -1); + if (arr2.fill) { + arr2.fill(0, -2, -1); + assert.deepEqual(arr1, arr2); + } + }); + + it("should mutate array like native", () => { + const arr = [1, 2, 3]; + const result = polyArrFill(arr, 0); + + assert.deepEqual(arr, [0, 0, 0]); + assert.strictEqual(result, arr); // Returns same reference + + if (arr.fill) { + const arr2 = [1, 2, 3]; + const result2 = arr2.fill(0); + assert.strictEqual(result2, arr2); + } + }); + + it("should handle empty array like native", () => { + const arr: number[] = []; + polyArrFill(arr, 5); + + assert.deepEqual(arr, []); + + if (arr.fill) { + const arr2: number[] = []; + arr2.fill(5); + assert.deepEqual(arr2, []); + } + }); + + it("should work with array-like objects", () => { + const arrayLike = { length: 4, 0: 1, 1: 2, 2: 3, 3: 4 }; + polyArrFill(arrayLike, 0, 1, 3); + + assert.equal(arrayLike[0], 1); + assert.equal(arrayLike[1], 0); + assert.equal(arrayLike[2], 0); + assert.equal(arrayLike[3], 4); + }); +}); diff --git a/lib/test/src/common/array/intersection.test.ts b/lib/test/src/common/array/intersection.test.ts new file mode 100644 index 00000000..e2e41ffd --- /dev/null +++ b/lib/test/src/common/array/intersection.test.ts @@ -0,0 +1,53 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { assert } from "@nevware21/tripwire-chai"; +import { arrIntersection } from "../../../../src/array/intersection"; + +describe("arrIntersection", () => { + it("should return common elements between two arrays", () => { + assert.deepEqual(arrIntersection([1, 2, 3], [2, 3, 4]), [2, 3]); + assert.deepEqual(arrIntersection(["a", "b"], ["b", "c"]), ["b"]); + }); + + it("should return common elements across multiple arrays", () => { + assert.deepEqual(arrIntersection([1, 2, 3], [2, 3], [3, 4]), [3]); + assert.deepEqual(arrIntersection([1, 2, 3, 4], [2, 3, 4], [3, 4, 5]), [3, 4]); + }); + + it("should return empty array when no common elements", () => { + assert.deepEqual(arrIntersection([1, 2], [3, 4]), []); + }); + + it("should return empty array when any array is empty", () => { + assert.deepEqual(arrIntersection([1, 2, 3], []), []); + }); + + it("should handle null and undefined", () => { + assert.deepEqual(arrIntersection(), []); + assert.deepEqual(arrIntersection(null, [1, 2]), []); + assert.deepEqual(arrIntersection(undefined, [1, 2]), []); + }); + + it("should work with array-like objects", () => { + const arrayLike = { length: 3, 0: 1, 1: 2, 2: 3 }; + assert.deepEqual(arrIntersection(arrayLike, [2, 3]), [2, 3]); + }); + + it("should use strict equality", () => { + assert.deepEqual(arrIntersection([1, 2, 3], ["1", "2"]), []); + }); + + it("should preserve order from first array", () => { + assert.deepEqual(arrIntersection([5, 3, 1, 4, 2], [1, 2, 3, 4, 5]), [5, 3, 1, 4, 2]); + }); + + it("should remove duplicates from result", () => { + assert.deepEqual(arrIntersection([1, 2, 2, 3, 3], [2, 3]), [2, 3]); + }); +}); diff --git a/lib/test/src/common/array/partition.test.ts b/lib/test/src/common/array/partition.test.ts new file mode 100644 index 00000000..d53fefbd --- /dev/null +++ b/lib/test/src/common/array/partition.test.ts @@ -0,0 +1,68 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { assert } from "@nevware21/tripwire-chai"; +import { arrPartition } from "../../../../src/array/partition"; + +describe("arrPartition", () => { + it("should split array based on predicate", () => { + const [evens, odds] = arrPartition([1, 2, 3, 4, 5], x => x % 2 === 0); + assert.deepEqual(evens, [2, 4]); + assert.deepEqual(odds, [1, 3, 5]); + }); + + it("should handle string length predicate", () => { + const [long, short] = arrPartition(["a", "bb", "ccc"], x => x.length > 1); + assert.deepEqual(long, ["bb", "ccc"]); + assert.deepEqual(short, ["a"]); + }); + + it("should handle all matching predicate", () => { + const [match, noMatch] = arrPartition([2, 4, 6], x => x % 2 === 0); + assert.deepEqual(match, [2, 4, 6]); + assert.deepEqual(noMatch, []); + }); + + it("should handle no matching predicate", () => { + const [match, noMatch] = arrPartition([1, 3, 5], x => x % 2 === 0); + assert.deepEqual(match, []); + assert.deepEqual(noMatch, [1, 3, 5]); + }); + + it("should handle empty array", () => { + const [match, noMatch] = arrPartition([], x => x > 0); + assert.deepEqual(match, []); + assert.deepEqual(noMatch, []); + }); + + it("should handle null and undefined", () => { + const [m1, n1] = arrPartition(null, x => x > 0); + assert.deepEqual(m1, []); + assert.deepEqual(n1, []); + + const [m2, n2] = arrPartition(undefined, x => x > 0); + assert.deepEqual(m2, []); + assert.deepEqual(n2, []); + }); + + it("should work with array-like objects", () => { + const arrayLike = { length: 4, 0: 1, 1: 2, 2: 3, 3: 4 }; + const [evens, odds] = arrPartition(arrayLike, x => x % 2 === 0); + assert.deepEqual(evens, [2, 4]); + assert.deepEqual(odds, [1, 3]); + }); + + it("should support thisArg", () => { + const threshold = { value: 3 }; + const [match, noMatch] = arrPartition([1, 2, 3, 4, 5], function(x) { + return x > this.value; + }, threshold); + assert.deepEqual(match, [4, 5]); + assert.deepEqual(noMatch, [1, 2, 3]); + }); +}); diff --git a/lib/test/src/common/array/reverse.test.ts b/lib/test/src/common/array/reverse.test.ts new file mode 100644 index 00000000..91446f68 --- /dev/null +++ b/lib/test/src/common/array/reverse.test.ts @@ -0,0 +1,76 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { assert } from "@nevware21/tripwire-chai"; +import { arrReverse } from "../../../../src/array/reverse"; + +describe("arrReverse", () => { + it("should reverse array in place", () => { + const arr = [1, 2, 3, 4]; + const result = arrReverse(arr); + + assert.deepEqual(result, [4, 3, 2, 1]); + assert.deepEqual(arr, [4, 3, 2, 1]); // Mutates original + assert.strictEqual(result, arr); // Returns same reference + }); + + it("should handle single element array", () => { + const arr = [1]; + arrReverse(arr); + + assert.deepEqual(arr, [1]); + }); + + it("should handle empty array", () => { + const arr: number[] = []; + arrReverse(arr); + + assert.deepEqual(arr, []); + }); + + it("should handle two element array", () => { + const arr = [1, 2]; + arrReverse(arr); + + assert.deepEqual(arr, [2, 1]); + }); + + it("should handle null and undefined", () => { + // arrReverse wraps native reverse which doesn't accept null/undefined + // These would throw TypeError in native implementation + assert.throws(() => arrReverse(null as any)); + assert.throws(() => arrReverse(undefined as any)); + }); + + it("should work with array-like objects after conversion", () => { + // Native reverse requires actual Array, not array-like + const arrayLike = { length: 3, 0: "a", 1: "b", 2: "c" }; + // Convert to array first using Array.from or arrSlice + const arr = Array.from(arrayLike as any); + const result = arrReverse(arr); + + assert.equal(result[0], "c"); + assert.equal(result[1], "b"); + assert.equal(result[2], "a"); + }); + + it("should reverse array of strings", () => { + const result = arrReverse(["h", "e", "l", "l", "o"]); + assert.deepEqual(result, ["o", "l", "l", "e", "h"]); + }); + + it("should match native Array.prototype.reverse", () => { + const arr1 = [1, 2, 3, 4, 5]; + const arr2 = [1, 2, 3, 4, 5]; + + arrReverse(arr1); + arr2.reverse(); + + assert.deepEqual(arr1, arr2); + }); +}); diff --git a/lib/test/src/common/array/rotate.test.ts b/lib/test/src/common/array/rotate.test.ts new file mode 100644 index 00000000..5b83c97f --- /dev/null +++ b/lib/test/src/common/array/rotate.test.ts @@ -0,0 +1,62 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { assert } from "@nevware21/tripwire-chai"; +import { arrRotate } from "../../../../src/array/rotate"; + +describe("arrRotate", () => { + it("should rotate array left by default", () => { + assert.deepEqual(arrRotate([1, 2, 3, 4, 5], 1), [2, 3, 4, 5, 1]); + assert.deepEqual(arrRotate([1, 2, 3, 4, 5], 2), [3, 4, 5, 1, 2]); + }); + + it("should rotate array right with negative positions", () => { + assert.deepEqual(arrRotate([1, 2, 3, 4, 5], -1), [5, 1, 2, 3, 4]); + assert.deepEqual(arrRotate([1, 2, 3, 4, 5], -2), [4, 5, 1, 2, 3]); + }); + + it("should handle rotation by length", () => { + assert.deepEqual(arrRotate([1, 2, 3], 3), [1, 2, 3]); + assert.deepEqual(arrRotate([1, 2, 3], -3), [1, 2, 3]); + }); + + it("should handle rotation greater than length", () => { + assert.deepEqual(arrRotate([1, 2, 3], 4), [2, 3, 1]); + assert.deepEqual(arrRotate([1, 2, 3], -4), [3, 1, 2]); + }); + + it("should handle zero rotation", () => { + assert.deepEqual(arrRotate([1, 2, 3], 0), [1, 2, 3]); + }); + + it("should handle empty array", () => { + assert.deepEqual(arrRotate([], 5), []); + }); + + it("should handle single element array", () => { + assert.deepEqual(arrRotate([1], 5), [1]); + }); + + it("should handle null and undefined", () => { + assert.deepEqual(arrRotate(null, 2), []); + assert.deepEqual(arrRotate(undefined, 2), []); + }); + + it("should work with array-like objects", () => { + const arrayLike = { length: 4, 0: "a", 1: "b", 2: "c", 3: "d" }; + assert.deepEqual(arrRotate(arrayLike, 1), ["b", "c", "d", "a"]); + }); + + it("should not modify original array", () => { + const original = [1, 2, 3, 4]; + const copy = [...original]; + arrRotate(original, 2); + + assert.deepEqual(original, copy); + }); +}); diff --git a/lib/test/src/common/array/shuffle_sample.test.ts b/lib/test/src/common/array/shuffle_sample.test.ts new file mode 100644 index 00000000..39bf2fd8 --- /dev/null +++ b/lib/test/src/common/array/shuffle_sample.test.ts @@ -0,0 +1,113 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { assert } from "@nevware21/tripwire-chai"; +import { arrShuffle } from "../../../../src/array/shuffle"; +import { arrSample } from "../../../../src/array/sample"; + +describe("arrShuffle", () => { + it("should return array with same elements", () => { + const original = [1, 2, 3, 4, 5]; + const shuffled = arrShuffle(original); + + assert.equal(shuffled.length, original.length); + original.forEach(val => { + assert.ok(shuffled.includes(val), `Should contain ${val}`); + }); + }); + + it("should not modify original array", () => { + const original = [1, 2, 3, 4, 5]; + const copy = [...original]; + arrShuffle(original); + + assert.deepEqual(original, copy); + }); + + it("should handle single element array", () => { + assert.deepEqual(arrShuffle([1]), [1]); + }); + + it("should handle empty array", () => { + assert.deepEqual(arrShuffle([]), []); + }); + + it("should throw for null and undefined", () => { + // arrSlice (used internally) throws TypeError for null/undefined + assert.throws(() => arrShuffle(null)); + assert.throws(() => arrShuffle(undefined)); + }); + + it("should work with array-like objects", () => { + const arrayLike = { length: 3, 0: "x", 1: "y", 2: "z" }; + const shuffled = arrShuffle(arrayLike); + + assert.equal(shuffled.length, 3); + assert.ok(shuffled.includes("x")); + assert.ok(shuffled.includes("y")); + assert.ok(shuffled.includes("z")); + }); + + it("should produce different results (statistical)", () => { + const original = [1, 2, 3, 4, 5]; + let different = false; + + // Try multiple times - should be different at least once + for (let i = 0; i < 10; i++) { + const shuffled = arrShuffle(original); + if (JSON.stringify(shuffled) !== JSON.stringify(original)) { + different = true; + break; + } + } + + assert.ok(different, "Should produce different ordering"); + }); +}); + +describe("arrSample", () => { + it("should return element from array", () => { + const arr = [1, 2, 3, 4, 5]; + const sample = arrSample(arr); + + assert.ok(arr.includes(sample), "Sample should be from array"); + }); + + it("should return single element for single item array", () => { + assert.equal(arrSample([42]), 42); + }); + + it("should return undefined for empty array", () => { + assert.isUndefined(arrSample([])); + }); + + it("should throw for null and undefined", () => { + // arrSample expects a valid array + assert.isUndefined(arrSample(null)); + assert.isUndefined(arrSample(undefined)); + }); + + it("should work with array-like objects", () => { + const arrayLike = { length: 3, 0: "x", 1: "y", 2: "z" }; + const sample = arrSample(arrayLike); + + assert.ok(["x", "y", "z"].includes(sample)); + }); + + it("should return different values (statistical)", () => { + const arr = [1, 2, 3, 4, 5]; + const samples = new Set(); + + // Sample many times - should get different values + for (let i = 0; i < 50; i++) { + samples.add(arrSample(arr)); + } + + assert.ok(samples.size > 1, "Should sample different values"); + }); +}); diff --git a/lib/test/src/common/array/take_drop.test.ts b/lib/test/src/common/array/take_drop.test.ts new file mode 100644 index 00000000..775d1f13 --- /dev/null +++ b/lib/test/src/common/array/take_drop.test.ts @@ -0,0 +1,141 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { assert } from "@nevware21/tripwire-chai"; +import { arrTake } from "../../../../src/array/take"; +import { arrDrop } from "../../../../src/array/drop"; +import { arrTakeWhile } from "../../../../src/array/take_while"; +import { arrDropWhile } from "../../../../src/array/drop_while"; + +describe("arrTake", () => { + it("should take first n elements", () => { + assert.deepEqual(arrTake([1, 2, 3, 4, 5], 3), [1, 2, 3]); + assert.deepEqual(arrTake(["a", "b", "c"], 2), ["a", "b"]); + }); + + it("should return all elements when n exceeds length", () => { + assert.deepEqual(arrTake([1, 2], 5), [1, 2]); + }); + + it("should return empty array when n is 0 or negative", () => { + assert.deepEqual(arrTake([1, 2, 3], 0), []); + assert.deepEqual(arrTake([1, 2, 3], -1), []); + }); + + it("should throw for null and undefined", () => { + // arrSlice (used internally) throws TypeError for null/undefined + assert.throws(() => arrTake(null, 2)); + assert.throws(() => arrTake(undefined, 2)); + }); + + it("should work with array-like objects", () => { + const arrayLike = { length: 4, 0: 1, 1: 2, 2: 3, 3: 4 }; + assert.deepEqual(arrTake(arrayLike, 2), [1, 2]); + }); +}); + +describe("arrDrop", () => { + it("should drop first n elements", () => { + assert.deepEqual(arrDrop([1, 2, 3, 4, 5], 2), [3, 4, 5]); + assert.deepEqual(arrDrop(["a", "b", "c"], 1), ["b", "c"]); + }); + + it("should return empty array when n exceeds length", () => { + assert.deepEqual(arrDrop([1, 2], 5), []); + }); + + it("should return all elements when n is 0 or negative", () => { + assert.deepEqual(arrDrop([1, 2, 3], 0), [1, 2, 3]); + assert.deepEqual(arrDrop([1, 2, 3], -1), [1, 2, 3]); + }); + + it("should throw for null and undefined", () => { + // arrSlice (used internally) throws TypeError for null/undefined + assert.throws(() => arrDrop(null, 2)); + assert.throws(() => arrDrop(undefined, 2)); + }); + + it("should work with array-like objects", () => { + const arrayLike = { length: 4, 0: 1, 1: 2, 2: 3, 3: 4 }; + assert.deepEqual(arrDrop(arrayLike, 2), [3, 4]); + }); +}); + +describe("arrTakeWhile", () => { + it("should take elements while predicate is true", () => { + assert.deepEqual(arrTakeWhile([1, 2, 3, 4, 1], x => x < 3), [1, 2]); + assert.deepEqual(arrTakeWhile([2, 4, 6, 1, 8], x => x % 2 === 0), [2, 4, 6]); + }); + + it("should return empty array when first element fails predicate", () => { + assert.deepEqual(arrTakeWhile([1, 2, 3], x => x > 5), []); + }); + + it("should return all elements when all match predicate", () => { + assert.deepEqual(arrTakeWhile([1, 2, 3], x => x < 5), [1, 2, 3]); + }); + + it("should handle empty array", () => { + assert.deepEqual(arrTakeWhile([], x => x > 0), []); + }); + + it("should handle null and undefined", () => { + assert.deepEqual(arrTakeWhile(null, x => x > 0), []); + assert.deepEqual(arrTakeWhile(undefined, x => x > 0), []); + }); + + it("should work with array-like objects", () => { + const arrayLike = { length: 4, 0: 1, 1: 2, 2: 3, 3: 4 }; + assert.deepEqual(arrTakeWhile(arrayLike, x => x < 3), [1, 2]); + }); + + it("should support thisArg", () => { + const threshold = { value: 3 }; + const result = arrTakeWhile([1, 2, 3, 4, 5], function(x) { + return x < this.value; + }, threshold); + assert.deepEqual(result, [1, 2]); + }); +}); + +describe("arrDropWhile", () => { + it("should drop elements while predicate is true", () => { + assert.deepEqual(arrDropWhile([1, 2, 3, 4, 1], x => x < 3), [3, 4, 1]); + assert.deepEqual(arrDropWhile([2, 4, 6, 1, 8], x => x % 2 === 0), [1, 8]); + }); + + it("should return all elements when first element fails predicate", () => { + assert.deepEqual(arrDropWhile([1, 2, 3], x => x > 5), [1, 2, 3]); + }); + + it("should return empty array when all match predicate", () => { + assert.deepEqual(arrDropWhile([1, 2, 3], x => x < 5), []); + }); + + it("should handle empty array", () => { + assert.deepEqual(arrDropWhile([], x => x > 0), []); + }); + + it("should handle null and undefined", () => { + assert.deepEqual(arrDropWhile(null, x => x > 0), []); + assert.deepEqual(arrDropWhile(undefined, x => x > 0), []); + }); + + it("should work with array-like objects", () => { + const arrayLike = { length: 4, 0: 1, 1: 2, 2: 3, 3: 4 }; + assert.deepEqual(arrDropWhile(arrayLike, x => x < 3), [3, 4]); + }); + + it("should support thisArg", () => { + const threshold = { value: 3 }; + const result = arrDropWhile([1, 2, 3, 4, 5], function(x) { + return x < this.value; + }, threshold); + assert.deepEqual(result, [3, 4, 5]); + }); +}); diff --git a/lib/test/src/common/array/union.test.ts b/lib/test/src/common/array/union.test.ts new file mode 100644 index 00000000..b6751c97 --- /dev/null +++ b/lib/test/src/common/array/union.test.ts @@ -0,0 +1,48 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { assert } from "@nevware21/tripwire-chai"; +import { arrUnion } from "../../../../src/array/union"; + +describe("arrUnion", () => { + it("should return unique elements from two arrays", () => { + assert.deepEqual(arrUnion([1, 2], [2, 3]), [1, 2, 3]); + assert.deepEqual(arrUnion(["a", "b"], ["b", "c"]), ["a", "b", "c"]); + }); + + it("should handle multiple arrays", () => { + assert.deepEqual(arrUnion([1, 2], [3, 4], [4, 5]), [1, 2, 3, 4, 5]); + }); + + it("should remove duplicates", () => { + assert.deepEqual(arrUnion([1, 1, 2], [2, 2, 3]), [1, 2, 3]); + }); + + it("should handle empty arrays", () => { + assert.deepEqual(arrUnion([], [1, 2]), [1, 2]); + assert.deepEqual(arrUnion([1, 2], []), [1, 2]); + assert.deepEqual(arrUnion([], []), []); + }); + + it("should handle single array", () => { + assert.deepEqual(arrUnion([1, 2, 3]), [1, 2, 3]); + }); + + it("should work with array-like objects", () => { + const arrayLike = { length: 2, 0: 1, 1: 2 }; + assert.deepEqual(arrUnion(arrayLike, [2, 3]), [1, 2, 3]); + }); + + it("should use strict equality", () => { + assert.deepEqual(arrUnion([1, 2], ["1", "2"]), [1, 2, "1", "2"]); + }); + + it("should preserve insertion order", () => { + assert.deepEqual(arrUnion([3, 1], [2, 4]), [3, 1, 2, 4]); + }); +}); diff --git a/lib/test/src/common/array/with.test.ts b/lib/test/src/common/array/with.test.ts new file mode 100644 index 00000000..ef5f7eb7 --- /dev/null +++ b/lib/test/src/common/array/with.test.ts @@ -0,0 +1,184 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { assert } from "@nevware21/tripwire-chai"; +import { arrWith, polyArrWith } from "../../../../src/array/with"; + +describe("arrWith", () => { + it("should return new array with element replaced", () => { + const arr = [1, 2, 3]; + const result = arrWith(arr, 1, 99); + + assert.deepEqual(result, [1, 99, 3]); + assert.deepEqual(arr, [1, 2, 3]); // Original unchanged + }); + + it("should handle negative indices", () => { + const arr = [1, 2, 3, 4, 5]; + assert.deepEqual(arrWith(arr, -1, 99), [1, 2, 3, 4, 99]); + assert.deepEqual(arrWith(arr, -2, 88), [1, 2, 3, 88, 5]); + }); + + it("should throw RangeError for out of bounds positive index", () => { + assert.throws(() => arrWith([1, 2, 3], 5, 99), RangeError); + assert.throws(() => arrWith([1, 2, 3], 3, 99), RangeError); + assert.throws(() => arrWith([1, 2, 3], 100, 0), RangeError); + }); + + it("should throw RangeError for out of bounds negative index", () => { + assert.throws(() => arrWith([1, 2, 3], -4, 99), RangeError); + assert.throws(() => arrWith([1, 2, 3], -10, 99), RangeError); + assert.throws(() => arrWith([1, 2, 3], -100, 0), RangeError); + }); + + it("should throw RangeError for empty array", () => { + assert.throws(() => arrWith([], 0, 99), RangeError); + assert.throws(() => arrWith([], -1, 99), RangeError); + }); + + it("should replace last element with -1", () => { + assert.deepEqual(arrWith([10, 20, 30], -1, 99), [10, 20, 99]); + }); + + it("should replace first element with -length", () => { + assert.deepEqual(arrWith([10, 20, 30], -3, 99), [99, 20, 30]); + }); + + it("should throw when index equals length", () => { + const arr = [1, 2, 3]; + assert.throws(() => arrWith(arr, arr.length, 99), RangeError); + }); + + it("should throw when negative index points beyond start", () => { + const arr = [1, 2, 3]; + assert.throws(() => arrWith(arr, -(arr.length + 1), 99), RangeError); + }); + + it("should handle index 0", () => { + assert.deepEqual(arrWith([1, 2, 3], 0, 99), [99, 2, 3]); + }); + + it("should throw for null and undefined array", () => { + // Native .with() throws TypeError for null/undefined + assert.throws(() => arrWith(null, 0, 99)); + assert.throws(() => arrWith(undefined, 0, 99)); + }); + + it("should work with array-like objects", () => { + const arrayLike = { length: 3, 0: "a", 1: "b", 2: "c" }; + assert.deepEqual(arrWith(arrayLike, 1, "x"), ["a", "x", "c"]); + }); + + it("should not modify original array", () => { + const original = [1, 2, 3]; + const copy = [...original]; + arrWith(original, 1, 99); + + assert.deepEqual(original, copy); + }); +}); + +describe("polyArrWith", () => { + it("should match native Array.prototype.with for valid indices", () => { + const arr: any = [1, 2, 3, 4, 5]; + + for (let i = 0; i < arr.length; i++) { + const polyResult = polyArrWith(arr, i, 99); + + if (arr.with) { + const nativeResult = arr.with(i, 99); + assert.deepEqual(polyResult, nativeResult, `Index ${i} should match`); + } else { + const expected = [...arr]; + expected[i] = 99; + assert.deepEqual(polyResult, expected); + } + } + }); + + it("should match native Array.prototype.with for negative indices", () => { + const arr: any = [1, 2, 3, 4, 5]; + + for (let i = -1; i >= -arr.length; i--) { + const polyResult = polyArrWith(arr, i, 99); + + if (arr.with) { + const nativeResult = arr.with(i, 99); + assert.deepEqual(polyResult, nativeResult, `Index ${i} should match`); + } else { + const expected = [...arr]; + expected[arr.length + i] = 99; + assert.deepEqual(polyResult, expected); + } + } + }); + + it("should throw RangeError for out of bounds like native", () => { + const arr: any = [1, 2, 3]; + + assert.throws(() => polyArrWith(arr, 5, 99), RangeError); + assert.throws(() => polyArrWith(arr, -10, 99), RangeError); + assert.throws(() => polyArrWith(arr, 3, 0), RangeError); // Index at length + assert.throws(() => polyArrWith(arr, -4, 0), RangeError); // Negative index beyond start + + if (arr.with) { + assert.throws(() => arr.with(5, 99), RangeError); + assert.throws(() => arr.with(-10, 99), RangeError); + } + }); + + it("should throw RangeError for empty array-like objects", () => { + const emptyArrayLike = { length: 0 }; + assert.throws(() => polyArrWith(emptyArrayLike, 0, 99), RangeError); + assert.throws(() => polyArrWith(emptyArrayLike, -1, 99), RangeError); + }); + + it("should handle boundary indices correctly", () => { + const arr = [10, 20, 30]; + + // Valid: index 0 to length-1 + assert.deepEqual(polyArrWith(arr, 0, 1), [1, 20, 30]); + assert.deepEqual(polyArrWith(arr, 1, 2), [10, 2, 30]); + assert.deepEqual(polyArrWith(arr, 2, 3), [10, 20, 3]); + + // Valid: negative indices from -1 to -length + assert.deepEqual(polyArrWith(arr, -1, 1), [10, 20, 1]); + assert.deepEqual(polyArrWith(arr, -2, 2), [10, 2, 30]); + assert.deepEqual(polyArrWith(arr, -3, 3), [3, 20, 30]); + }); + + it("should not modify original array like native", () => { + const arr: any = [1, 2, 3]; + const copy = [...arr]; + + polyArrWith(arr, 1, 99); + assert.deepEqual(arr, copy); + + if (arr.with) { + arr.with(1, 99); + assert.deepEqual(arr, copy); + } + }); + + it("should work with array-like objects", () => { + const arrayLike = { length: 3, 0: 10, 1: 20, 2: 30 }; + const result = polyArrWith(arrayLike, 1, 99); + + assert.deepEqual(result, [10, 99, 30]); + }); + + it("should handle empty array correctly", () => { + const arr: number[] = []; + + assert.throws(() => polyArrWith(arr, 0, 99), RangeError); + + if ((arr as any).with) { + assert.throws(() => (arr as any).with(0, 99), RangeError); + } + }); +}); diff --git a/lib/test/src/common/array/zip_unzip.test.ts b/lib/test/src/common/array/zip_unzip.test.ts new file mode 100644 index 00000000..d7d412a7 --- /dev/null +++ b/lib/test/src/common/array/zip_unzip.test.ts @@ -0,0 +1,96 @@ +/* + * @nevware21/ts-utils + * https://github.com/nevware21/ts-utils + * + * Copyright (c) 2026 NevWare21 Solutions LLC + * Licensed under the MIT license. + */ + +import { assert } from "@nevware21/tripwire-chai"; +import { arrZip } from "../../../../src/array/zip"; +import { arrUnzip } from "../../../../src/array/unzip"; + +describe("arrZip", () => { + it("should combine arrays by index", () => { + assert.deepEqual(arrZip([1, 2], ["a", "b"]), [[1, "a"], [2, "b"]]); + assert.deepEqual(arrZip([1, 2, 3], ["a", "b", "c"]), [[1, "a"], [2, "b"], [3, "c"]]); + }); + + it("should handle arrays of different lengths", () => { + const result = arrZip([1, 2, 3], ["a", "b"]); + assert.equal(result.length, 2); + assert.deepEqual(result, [[1, "a"], [2, "b"]]); + }); + + it("should handle three or more arrays", () => { + assert.deepEqual( + arrZip([1, 2], ["a", "b"], [true, false]), + [[1, "a", true], [2, "b", false]] + ); + }); + + it("should handle single array", () => { + assert.deepEqual(arrZip([1, 2, 3]), [[1], [2], [3]]); + }); + + it("should return empty array when empty arrays provided", () => { + assert.deepEqual(arrZip([], []), []); + }); + + it("should handle null and undefined", () => { + assert.deepEqual(arrZip(null), []); + assert.deepEqual(arrZip(undefined), []); + assert.deepEqual(arrZip([1, 2], null), [[1], [2]]); + }); + + it("should work with array-like objects", () => { + const arrayLike = { length: 2, 0: 1, 1: 2 }; + assert.deepEqual(arrZip(arrayLike, ["a", "b"]), [[1, "a"], [2, "b"]]); + }); +}); + +describe("arrUnzip", () => { + it("should reverse zip operation", () => { + const zipped = [[1, "a"], [2, "b"], [3, "c"]]; + assert.deepEqual(arrUnzip(zipped), [[1, 2, 3], ["a", "b", "c"]]); + }); + + it("should handle arrays with more than 2 elements", () => { + const zipped = [[1, "a", true], [2, "b", false]]; + assert.deepEqual(arrUnzip(zipped), [[1, 2], ["a", "b"], [true, false]]); + }); + + it("should handle irregular arrays", () => { + const irregular = [[1, "a"], [2], [3, "c", true]]; + const result = arrUnzip(irregular); + assert.equal(result[0][0], 1); + assert.equal(result[0][1], 2); + assert.equal(result[0][2], 3); + assert.equal(result[1][0], "a"); + assert.isUndefined(result[1][1]); + assert.equal(result[1][2], "c"); + }); + + it("should return empty array for empty array", () => { + assert.deepEqual(arrUnzip([]), []); + }); + + it("should handle null and undefined", () => { + assert.deepEqual(arrUnzip(null), []); + assert.deepEqual(arrUnzip(undefined), []); + }); + + it("should handle single element arrays", () => { + assert.deepEqual(arrUnzip([[1], [2], [3]]), [[1, 2, 3]]); + }); + + it("should be inverse of arrZip", () => { + const arr1 = [1, 2, 3]; + const arr2 = ["a", "b", "c"]; + const zipped = arrZip(arr1, arr2); + const unzipped = arrUnzip(zipped); + + assert.deepEqual(unzipped[0], arr1); + assert.deepEqual(unzipped[1], arr2); + }); +}); diff --git a/lib/test/src/common/helpers/array.test.ts b/lib/test/src/common/helpers/array.test.ts index 489f107e..707bf9a0 100644 --- a/lib/test/src/common/helpers/array.test.ts +++ b/lib/test/src/common/helpers/array.test.ts @@ -8,8 +8,12 @@ import { assert } from "@nevware21/tripwire-chai"; import { arrAppend } from "../../../../src/array/append"; +import { arrChunk } from "../../../../src/array/chunk"; +import { arrCompact } from "../../../../src/array/compact"; import { arrFind, arrFindIndex, arrFindLast, arrFindLastIndex } from "../../../../src/array/find"; +import { arrFlatten } from "../../../../src/array/flatten"; import { arrFrom } from "../../../../src/array/from"; +import { arrGroupBy } from "../../../../src/array/groupBy"; import { arrSome } from "../../../../src/array/some"; import { arrForEach } from "../../../../src/array/forEach"; import { arrContains, arrIncludes } from "../../../../src/array/includes"; @@ -18,6 +22,7 @@ import { arrEvery, arrFilter } from "../../../../src/array/every"; import { arrMap } from "../../../../src/array/map"; import { arrReduce } from "../../../../src/array/reduce"; import { arrSlice } from "../../../../src/array/slice"; +import { arrUnique } from "../../../../src/array/unique"; import { dumpObj } from "../../../../src/helpers/diagnostics"; describe("array helpers", () => { @@ -973,6 +978,202 @@ describe("array helpers", () => { }); }); + describe("arrUnique", () => { + it("removes duplicate primitives", () => { + assert.deepEqual(arrUnique([1, 2, 2, 3, 1, 4]), [1, 2, 3, 4]); + assert.deepEqual(arrUnique(["a", "b", "a", "c"]), ["a", "b", "c"]); + assert.deepEqual(arrUnique([1, "1", 1, "1"]), [1, "1"]); + }); + + it("preserves insertion order", () => { + assert.deepEqual(arrUnique([3, 1, 2, 1, 3]), [3, 1, 2]); + assert.deepEqual(arrUnique(["z", "a", "z", "b"]), ["z", "a", "b"]); + }); + + it("handles empty and single element arrays", () => { + assert.deepEqual(arrUnique([]), []); + assert.deepEqual(arrUnique([1]), [1]); + }); + + it("handles null and undefined", () => { + assert.deepEqual(arrUnique(null), []); + assert.deepEqual(arrUnique(undefined), []); + }); + + it("handles array-like objects", () => { + assert.deepEqual(arrUnique({ length: 4, 0: 1, 1: 2, 2: 2, 3: 3 }), [1, 2, 3]); + assert.deepEqual(arrUnique({ length: 3, 0: "a", 1: "b", 2: "a" }), ["a", "b"]); + }); + + it("uses strict equality", () => { + assert.deepEqual(arrUnique([0, false, 0, false]), [0, false]); + assert.deepEqual(arrUnique([null, undefined, null]), [null, undefined]); + }); + }); + + describe("arrCompact", () => { + it("removes falsy values", () => { + assert.deepEqual(arrCompact([0, 1, false, 2, "", 3, null, undefined, 4]), [1, 2, 3, 4]); + assert.deepEqual(arrCompact([false, 0, "", null, undefined]), []); + }); + + it("preserves truthy values", () => { + assert.deepEqual(arrCompact([1, 2, 3]), [1, 2, 3]); + assert.deepEqual(arrCompact(["a", "b", "c"]), ["a", "b", "c"]); + assert.deepEqual(arrCompact([true, true, true]), [true, true, true]); + }); + + it("handles empty arrays", () => { + assert.deepEqual(arrCompact([]), []); + }); + + it("handles null and undefined", () => { + assert.deepEqual(arrCompact(null), []); + assert.deepEqual(arrCompact(undefined), []); + }); + + it("handles array-like objects", () => { + assert.deepEqual(arrCompact({ length: 5, 0: 0, 1: 1, 2: false, 3: 2, 4: null }), [1, 2]); + assert.deepEqual(arrCompact({ length: 3, 0: "a", 1: "", 2: "b" }), ["a", "b"]); + }); + + it("preserves numbers and strings properly", () => { + assert.deepEqual(arrCompact([1, 0, 2]), [1, 2]); + assert.deepEqual(arrCompact(["hello", "", "world"]), ["hello", "world"]); + }); + }); + + describe("arrFlatten", () => { + it("flattens with default depth of 1", () => { + assert.deepEqual(arrFlatten([1, [2, 3], [4, [5, 6]]]), [1, 2, 3, 4, [5, 6]]); + assert.deepEqual(arrFlatten([1, [2], 3]), [1, 2, 3]); + }); + + it("flattens with specified depth", () => { + assert.deepEqual(arrFlatten([1, [2, 3], [4, [5, 6]]], 2), [1, 2, 3, 4, 5, 6]); + assert.deepEqual(arrFlatten([1, [2, [3, [4]]]], 2), [1, 2, 3, [4]]); + }); + + it("flattens with infinite depth", () => { + assert.deepEqual(arrFlatten([1, [2, 3], [4, [5, 6]]], Infinity), [1, 2, 3, 4, 5, 6]); + assert.deepEqual(arrFlatten([1, [2, [3, [4, [5]]]]], Infinity), [1, 2, 3, 4, 5]); + }); + + it("handles non-arrays when depth is 0", () => { + assert.deepEqual(arrFlatten([1, [2, 3]], 0), [1, [2, 3]]); + }); + + it("preserves non-nested arrays", () => { + assert.deepEqual(arrFlatten([1, 2, 3]), [1, 2, 3]); + }); + + it("handles empty arrays", () => { + assert.deepEqual(arrFlatten([]), []); + }); + + it("handles null and undefined", () => { + assert.deepEqual(arrFlatten(null), []); + assert.deepEqual(arrFlatten(undefined), []); + }); + + it("handles array-like objects", () => { + assert.deepEqual(arrFlatten({ length: 2, 0: 1, 1: [2, 3] }), [1, 2, 3]); + }); + }); + + describe("arrGroupBy", () => { + it("groups by simple criteria", () => { + const numbers = [1, 2, 3, 4, 5, 6]; + const grouped = arrGroupBy(numbers, (n) => n % 2 === 0 ? "even" : "odd"); + assert.deepEqual(grouped["odd"], [1, 3, 5]); + assert.deepEqual(grouped["even"], [2, 4, 6]); + }); + + it("groups objects by property", () => { + const people = [ + { name: "Alice", age: 30 }, + { name: "Bob", age: 25 }, + { name: "Charlie", age: 30 } + ]; + const byAge = arrGroupBy(people, (p) => p.age); + assert.equal(byAge["25"].length, 1); + assert.equal(byAge["30"].length, 2); + assert.equal(byAge["25"][0].name, "Bob"); + }); + + it("handles empty arrays", () => { + const grouped = arrGroupBy([], (x) => x); + assert.deepEqual(grouped, {}); + }); + + it("handles null and undefined input", () => { + assert.deepEqual(arrGroupBy(null as any, (x: any) => x), {}); + assert.deepEqual(arrGroupBy(undefined as any, (x: any) => x), {}); + }); + + it("handles null callback", () => { + assert.deepEqual(arrGroupBy([1, 2, 3], null as any), {}); + }); + + it("supports thisArg parameter", () => { + const context = { multiplier: 2 }; + const numbers = [1, 2, 3, 4]; + const grouped = arrGroupBy(numbers, function(n) { + return (n * (this as any).multiplier) % 2 === 0 ? "even" : "odd"; + }, context); + assert.ok(grouped["odd"] || grouped["even"]); + }); + + it("handles array-like objects", () => { + const arrayLike = { length: 4, 0: 1, 1: 2, 2: 3, 3: 4 }; + const grouped = arrGroupBy(arrayLike, (n) => n % 2 === 0 ? "even" : "odd"); + assert.equal(grouped["odd"].length, 2); + assert.equal(grouped["even"].length, 2); + }); + }); + + describe("arrChunk", () => { + it("chunks array into specified size", () => { + assert.deepEqual(arrChunk([1, 2, 3, 4, 5, 6, 7], 2), [[1, 2], [3, 4], [5, 6], [7]]); + assert.deepEqual(arrChunk([1, 2, 3, 4, 5], 3), [[1, 2, 3], [4, 5]]); + }); + + it("handles chunk size of 1", () => { + assert.deepEqual(arrChunk([1, 2, 3], 1), [[1], [2], [3]]); + }); + + it("handles chunk size larger than array", () => { + assert.deepEqual(arrChunk([1, 2, 3], 5), [[1, 2, 3]]); + }); + + it("handles empty arrays", () => { + assert.deepEqual(arrChunk([], 2), []); + }); + + it("handles null and undefined", () => { + assert.deepEqual(arrChunk(null, 2), []); + assert.deepEqual(arrChunk(undefined, 2), []); + }); + + it("handles invalid chunk size", () => { + assert.deepEqual(arrChunk([1, 2, 3], 0), []); + assert.deepEqual(arrChunk([1, 2, 3], -1), []); + }); + + it("handles array-like objects", () => { + const arrayLike = { length: 5, 0: "a", 1: "b", 2: "c", 3: "d", 4: "e" }; + assert.deepEqual(arrChunk(arrayLike, 2), [["a", "b"], ["c", "d"], ["e"]]); + }); + + it("preserves exact chunk boundaries", () => { + const data = [10, 20, 30, 40, 50, 60]; + const chunks = arrChunk(data, 3); + assert.equal(chunks.length, 2); + assert.deepEqual(chunks[0], [10, 20, 30]); + assert.deepEqual(chunks[1], [40, 50, 60]); + }); + }); + function _expectThrow(cb: () => void): Error { try { cb(); diff --git a/lib/test/src/common/helpers/base.test.ts b/lib/test/src/common/helpers/base.test.ts index 3a330331..fcf94efc 100644 --- a/lib/test/src/common/helpers/base.test.ts +++ b/lib/test/src/common/helpers/base.test.ts @@ -8,7 +8,7 @@ import { assert } from "@nevware21/tripwire-chai"; import { - isArray, isBoolean, isDate, isDefined, isFunction, isNullOrUndefined, isNumber, isObject, isString, isTypeof, + isArray, isArrayLike, isBoolean, isDate, isDefined, isFunction, isNullOrUndefined, isNumber, isObject, isString, isTypeof, isUndefined, isRegExp, isFile, isFormData, isBlob, isArrayBuffer, isError, isPromiseLike, isPromise, isNotTruthy, isTruthy, isStrictUndefined, isStrictNullOrUndefined, isPrimitive, isPrimitiveType, isMap } from "../../../../src/helpers/base"; @@ -639,6 +639,75 @@ describe("base helpers", () => { }); }); + describe("isArrayLike", () => { + it("Validate values", () => { + assert.equal(isArrayLike(null), false, "Checking null"); + assert.equal(isArrayLike(undefined), false, "Checking undefined"); + assert.equal(isArrayLike("null"), true, "Checking 'null' (strings are array-like)"); + assert.equal(isArrayLike("undefined"), true, "Checking 'undefined' (strings are array-like)"); + assert.equal(isArrayLike("hello"), true, "Checking 'hello' (strings are array-like)"); + assert.equal(isArrayLike(""), true, "Checking '' (empty string is array-like)"); + assert.equal(isArrayLike(new Date()), false, "Checking Date"); + assert.equal(isArrayLike(1), false, "Checking 1"); + assert.equal(isArrayLike(0), false, "Checking 0"); + assert.equal(isArrayLike(_dummyFunction), false, "Checking _dummyFunction"); + assert.equal(isArrayLike([]), true, "Checking []"); + assert.equal(isArrayLike([1, 2, 3]), true, "Checking [1, 2, 3]"); + assert.equal(isArrayLike(new Array(1)), true, "Checking new Array(1)"); + assert.equal(isArrayLike(true), false, "Checking true"); + assert.equal(isArrayLike(false), false, "Checking false"); + assert.equal(isArrayLike("true"), true, "Checking 'true'"); + assert.equal(isArrayLike("false"), true, "Checking 'false'"); + assert.equal(isArrayLike(new Boolean(true)), false, "Checking new Boolean(true)"); + assert.equal(isArrayLike(new Boolean(false)), false, "Checking new Boolean(false)"); + assert.equal(isArrayLike(new Boolean("true")), false, "Checking new Boolean('true')"); + assert.equal(isArrayLike(new Boolean("false")), false, "Checking new Boolean('false')"); + assert.equal(isArrayLike(/[a-z]/g), false, "Checking '/[a-z]/g'"); + assert.equal(isArrayLike(new RegExp("")), false, "Checking new RegExp('')"); + _isFileCheck(isArrayLike, false); + _isFormDataCheck(isArrayLike, false); + _isBlobCheck(isArrayLike, false); + assert.equal(isArrayLike(new ArrayBuffer(0)), false, "Checking new ArrayBuffer([])"); + assert.equal(isArrayLike(new Error("Test Error")), false, "Checking new Error('')"); + assert.equal(isArrayLike(new TypeError("Test TypeError")), false, "Checking new TypeError('')"); + assert.equal(isArrayLike(new TestError("Test TestError")), false, "Checking new TestError('')"); + assert.equal(isArrayLike(_dummyError()), false, "Checking dummy error (object that looks like an error)"); + assert.equal(isArrayLike(Promise.reject()), false, "Checking Promise.reject"); + assert.equal(isArrayLike(Promise.resolve()), false, "Checking Promise.resolve"); + assert.equal(isArrayLike(new Promise(() => {})), false, "Checking new Promise(() => {})"); + assert.equal(isArrayLike(_simplePromise()), false, "Checking _simplePromise"); + assert.equal(isArrayLike(_simplePromiseLike()), false, "Checking _simplePromiseLike"); + assert.equal(isArrayLike(Object.create(null)), false, "Checking Object.create(null)"); + assert.equal(isArrayLike(polyObjCreate(null)), false, "Checking polyObjCreate(null)"); + }); + + it("Validate array-like objects", () => { + // Object with length property + const arrayLike = { length: 3, 0: "a", 1: "b", 2: "c" }; + assert.equal(isArrayLike(arrayLike), true, "Checking array-like object"); + + // Object with length but no numeric indices + const hasLength = { length: 5 }; + assert.equal(isArrayLike(hasLength), true, "Checking object with length property"); + + // Object with length but it's not a number + const lengthNotNumber = { length: "3" }; + assert.equal(isArrayLike(lengthNotNumber), false, "Checking object with non-numeric length"); + + // Object with no length property + const noLength = { 0: "a", 1: "b" }; + assert.equal(isArrayLike(noLength), false, "Checking object with numeric indices but no length"); + + // Object with negative length + const negativeLength = { length: -1 }; + assert.equal(isArrayLike(negativeLength), false, "Checking object with negative length"); + + // Object with length 0 + const emptyLength = { length: 0 }; + assert.equal(isArrayLike(emptyLength), true, "Checking object with length 0"); + }); + }); + describe("isDate", () => { it("Validate values", () => { assert.equal(isDate(null), false, "Checking null");