@@ -3,35 +3,53 @@ import { getValueByPath } from "./obj.utils";
33/**
44 * Splits an array into chunks of the specified size.
55 *
6- * @param arr - The array to chunk.
7- * @param size - The number of elements per chunk.
8- * @returns A new array of chunked arrays.
6+ * @template T The type of array elements.
7+ * @param {T[] } array - The array to split into chunks.
8+ * @param {number } size - The number of elements per chunk.
9+ * @returns {T[][] } A new array containing chunked arrays.
10+ * @throws {TypeError } If array is not an array.
11+ * @throws {Error } If chunk size is not a positive integer.
912 */
10- export const chunk = < T > ( arr : T [ ] , size : number ) : T [ ] [ ] => {
11- if ( ! Array . isArray ( arr ) ) return [ ] ;
12- if ( size <= 0 ) throw new Error ( "Chunk size must be greater than zero" ) ;
13- return Array . from ( { length : Math . ceil ( arr . length / size ) } , ( _ , i ) =>
14- arr . slice ( i * size , i * size + size ) ,
13+ export const chunk = < T > ( array : T [ ] , size : number ) : T [ ] [ ] => {
14+ if ( ! Array . isArray ( array ) ) throw new TypeError ( "Expected an array" ) ;
15+ if ( ! array . length ) return [ ] ;
16+ if ( ! Number . isInteger ( size ) || size <= 0 )
17+ throw new Error ( "Chunk size must be a positive integer" ) ;
18+ return Array . from ( { length : Math . ceil ( array . length / size ) } , ( _ , i ) =>
19+ array . slice ( i * size , i * size + size ) ,
1520 ) ;
1621} ;
1722
1823/**
1924 * Removes duplicate values from an array.
25+ * Optionally enforces uniqueness by a key function.
2026 *
21- * @param array - The input array.
22- * @returns A new array with unique values.
27+ * @template T The type of array elements.
28+ * @param {T[] } array - The input array.
29+ * @param {(item: T) => unknown } [keyFn] - Optional function to determine uniqueness by key.
30+ * @returns {T[] } A new array with unique values.
2331 */
24- export function unique < T > ( array : T [ ] ) : T [ ] {
25- return Array . from ( new Set ( array ) ) ;
32+ export function unique < T > ( array : T [ ] , keyFn ?: ( item : T ) => unknown ) : T [ ] {
33+ if ( ! Array . isArray ( array ) || array . length === 0 ) return [ ] ;
34+ if ( ! keyFn ) return Array . from ( new Set ( array ) ) ;
35+ const seen = new Set < unknown > ( ) ;
36+ return array . filter ( ( item ) => {
37+ const key = keyFn ( item ) ;
38+ if ( seen . has ( key ) ) return false ;
39+ seen . add ( key ) ;
40+ return true ;
41+ } ) ;
2642}
2743
2844/**
29- * Deeply flattens a nested array of arbitrary depth .
45+ * Deeply flattens a nested array to a single-level array .
3046 *
31- * @param array - The input nested array.
32- * @returns A deeply flattened array.
47+ * @template T The leaf type of array elements.
48+ * @param {any[] } array - The (possibly deeply nested) input array.
49+ * @returns {T[] } A deeply flattened array.
3350 */
3451export function flattenDeep < T > ( array : any [ ] ) : T [ ] {
52+ if ( ! Array . isArray ( array ) ) return [ ] ;
3553 return array . reduce < T [ ] > ( ( acc , val ) => {
3654 if ( Array . isArray ( val ) ) {
3755 acc . push ( ...flattenDeep < T > ( val ) ) ;
@@ -43,45 +61,70 @@ export function flattenDeep<T>(array: any[]): T[] {
4361}
4462
4563/**
46- * Returns a random element from an array.
64+ * Returns a random element from an array, or undefined if empty .
4765 *
48- * @param array - The input array.
49- * @returns A randomly selected item, or undefined if empty.
66+ * @template T The type of array elements.
67+ * @param {T[] } array - The input array.
68+ * @returns {T | undefined } A randomly selected item, or undefined if array is empty or not an array.
5069 */
5170export function random < T > ( array : T [ ] ) : T | undefined {
52- if ( array . length === 0 ) return undefined ;
71+ if ( ! Array . isArray ( array ) || array . length === 0 ) return undefined ;
5372 return array [ Math . floor ( Math . random ( ) * array . length ) ] ;
5473}
5574
75+ /* eslint-disable no-redeclare */
5676/**
57- * Groups items in an array by a key selector .
77+ * Groups items in an array by a key or key function .
5878 *
59- * @param array - The input array.
60- * @param keyFn - A function that returns the key for each item.
61- * @returns An object mapping keys to grouped arrays.
79+ * @template T The type of array elements.
80+ * @overload
81+ * @param {T[] } array - The array to group.
82+ * @param {keyof T } key - Property key to group by.
83+ * @returns {Record<string, T[]> }
84+ * @overload
85+ * @param {T[] } array - The array to group.
86+ * @param {(item: T) => string | number | symbol } keyFn - Function to generate group key from item.
87+ * @returns {Record<K, T[]> }
88+ * @param {T[] } array - The array to group.
89+ * @param {keyof T | ((item: T) => string | number | symbol) } keyOrFn - Property key or key-generating function.
90+ * @returns {Record<string | number | symbol, T[]> } An object mapping group keys to item arrays.
6291 */
92+ export function groupBy < T > ( array : T [ ] , key : keyof T ) : Record < string , T [ ] > ;
6393export function groupBy < T , K extends string | number | symbol > (
6494 array : T [ ] ,
6595 keyFn : ( item : T ) => K ,
66- ) : Record < K , T [ ] > {
96+ ) : Record < K , T [ ] > ;
97+ export function groupBy < T > (
98+ array : T [ ] ,
99+ keyOrFn : keyof T | ( ( item : T ) => string | number | symbol ) ,
100+ ) : Record < string | number | symbol , T [ ] > {
101+ if ( ! Array . isArray ( array ) || array . length === 0 ) return { } ;
102+ const keyFn =
103+ typeof keyOrFn === "function"
104+ ? keyOrFn
105+ : ( item : T ) => item [ keyOrFn as keyof T ] ;
67106 return array . reduce (
68107 ( acc , item ) => {
69- const key = keyFn ( item ) ;
108+ const key = keyFn ( item ) as string | number | symbol ;
70109 if ( ! acc [ key ] ) acc [ key ] = [ ] ;
71110 acc [ key ] . push ( item ) ;
72111 return acc ;
73112 } ,
74- { } as Record < K , T [ ] > ,
113+ { } as Record < string | number | symbol , T [ ] > ,
75114 ) ;
76115}
116+ /* eslint-enable no-redeclare */
77117
78118/**
79119 * Shuffles an array using the Fisher-Yates algorithm.
80120 *
81- * @param array - The input array.
82- * @returns A new shuffled array.
121+ * @template T The type of array elements.
122+ * @param {T[] } array - The input array.
123+ * @returns {T[] } A new shuffled array.
124+ * @throws {TypeError } If array is not an array.
83125 */
84126export function shuffle < T > ( array : T [ ] ) : T [ ] {
127+ if ( ! Array . isArray ( array ) ) throw new TypeError ( "Expected an array" ) ;
85128 const copy = array . slice ( ) ;
86129 for ( let i = copy . length - 1 ; i > 0 ; i -- ) {
87130 const j = Math . floor ( Math . random ( ) * ( i + 1 ) ) ;
@@ -91,81 +134,98 @@ export function shuffle<T>(array: T[]): T[] {
91134}
92135
93136/**
94- * Returns values in A that are not in B .
137+ * Returns an array of property values from an array of objects .
95138 *
96- * @param a - First array.
97- * @param b - Second array.
98- * @returns Elements in A that are not in B.
139+ * @template T The type of array elements.
140+ * @template K The object property to pluck.
141+ * @param {T[] } array - The input array.
142+ * @param {K } key - The property name to pluck.
143+ * @returns {T[K][] } Array of property values.
144+ */
145+ export function pluck < T , K extends keyof T > ( array : T [ ] , key : K ) : T [ K ] [ ] {
146+ if ( ! Array . isArray ( array ) ) return [ ] ;
147+ return array . map ( ( item ) => item [ key ] ) ;
148+ }
149+
150+ /**
151+ * Returns values in array A that are not in array B.
152+ *
153+ * @template T The type of array elements.
154+ * @param {T[] } a - First array.
155+ * @param {T[] } b - Second array.
156+ * @returns {T[] } Elements in A that are not in B.
99157 */
100158export function difference < T > ( a : T [ ] , b : T [ ] ) : T [ ] {
159+ if ( ! Array . isArray ( a ) || ! Array . isArray ( b ) ) return [ ] ;
101160 const setB = new Set ( b ) ;
102161 return a . filter ( ( item ) => ! setB . has ( item ) ) ;
103162}
104163
105164/**
106- * Returns common values between A and B.
165+ * Returns common values between arrays A and B.
107166 *
108- * @param a - First array.
109- * @param b - Second array.
110- * @returns Elements that exist in both arrays.
167+ * @template T The type of array elements.
168+ * @param {T[] } a - First array.
169+ * @param {T[] } b - Second array.
170+ * @returns {T[] } Elements that exist in both arrays.
111171 */
112172export function intersect < T > ( a : T [ ] , b : T [ ] ) : T [ ] {
173+ if ( ! Array . isArray ( a ) || ! Array . isArray ( b ) ) return [ ] ;
113174 const setB = new Set ( b ) ;
114175 return a . filter ( ( item ) => setB . has ( item ) ) ;
115176}
116177
117178/**
118179 * Sorts an array of objects by a nested key using Merge Sort (O(n log n)).
180+ * Missing/undefined keys are sorted to the "end" (asc) or "start" (desc).
119181 *
120- * @template T - Object type of the array elements.
121- * @param array - Array of objects to sort.
122- * @param key - Dot-notated key to sort by (e.g. 'profile.age').
123- * @param direction - Sort direction: 'asc' or 'desc'.
124- * @returns A new sorted array.
182+ * @template T The type of array elements (objects).
183+ * @param {T[] } array - Array of objects to sort.
184+ * @param {string | ((item: T) => any) } key - Dot-notated key (e.g., "profile.age") or function.
185+ * @param {"asc" | "desc" } [direction="asc"] - Sort direction: 'asc' or 'desc'.
186+ * @returns {T[] } A new sorted array.
187+ * @throws {TypeError } If array is not an array.
125188 */
126- export function mergeSort < T extends object > (
189+ export function mergeSort < T > (
127190 array : T [ ] ,
128- key : string ,
191+ key : string | ( ( item : T ) => any ) ,
129192 direction : "asc" | "desc" = "asc" ,
130193) : T [ ] {
194+ if ( ! Array . isArray ( array ) ) throw new TypeError ( "Expected array" ) ;
131195 if ( array . length <= 1 ) return array . slice ( ) ;
132196
133- const compare = ( a : T , b : T ) => {
134- const aVal = getValueByPath ( a , key ) ;
135- const bVal = getValueByPath ( b , key ) ;
197+ const keyFn =
198+ typeof key === "function"
199+ ? key
200+ : ( item : T ) => getValueByPath ( item as object , key ) ;
136201
202+ const compare = ( a : T , b : T ) => {
203+ const aVal = keyFn ( a ) ;
204+ const bVal = keyFn ( b ) ;
205+ // Sorts undefined/null last for "asc", first for "desc"
137206 if ( aVal === bVal ) return 0 ;
138- if ( aVal == null ) return 1 ;
139- if ( bVal == null ) return - 1 ;
140-
207+ if ( aVal == null ) return direction === "asc" ? 1 : - 1 ;
208+ if ( bVal == null ) return direction === "asc" ? - 1 : 1 ;
141209 return direction === "asc" ? ( aVal < bVal ? - 1 : 1 ) : aVal > bVal ? - 1 : 1 ;
142210 } ;
143211
144212 const merge = ( left : T [ ] , right : T [ ] ) : T [ ] => {
145213 const result : T [ ] = [ ] ;
146214 let i = 0 ,
147215 j = 0 ;
148-
149216 while ( i < left . length && j < right . length ) {
150- if ( compare ( left [ i ] , right [ j ] ) <= 0 ) {
151- result . push ( left [ i ++ ] ) ;
152- } else {
153- result . push ( right [ j ++ ] ) ;
154- }
217+ if ( compare ( left [ i ] , right [ j ] ) <= 0 ) result . push ( left [ i ++ ] ) ;
218+ else result . push ( right [ j ++ ] ) ;
155219 }
156-
157220 return result . concat ( left . slice ( i ) ) . concat ( right . slice ( j ) ) ;
158221 } ;
159222
160223 const sort = ( arr : T [ ] ) : T [ ] => {
161224 if ( arr . length <= 1 ) return arr ;
162-
163225 const mid = Math . floor ( arr . length / 2 ) ;
164226 const left = sort ( arr . slice ( 0 , mid ) ) ;
165227 const right = sort ( arr . slice ( mid ) ) ;
166-
167228 return merge ( left , right ) ;
168229 } ;
169-
170230 return sort ( array ) ;
171231}
0 commit comments