|
1 | 1 | import { isType } from '../is/isType' |
2 | 2 |
|
3 | 3 | /** |
4 | | - * |
| 4 | + * 数组去重函数 |
5 | 5 | * @param { any[] } array 数组 |
| 6 | + * @param { string[] } keys 可选,指定比较的键路径数组,如 ['name', 'target.age']。如果指定了keys,则只要这些指定的键值都相同,就认为是同一项进行去重 |
6 | 7 | * @returns 去重后的数组 |
| 8 | + * @example |
| 9 | + * // 基本去重 |
| 10 | + * uniqueArray([1, 2, 2, 3]) // [1, 2, 3] |
| 11 | + * |
| 12 | + * // 按指定键去重 |
| 13 | + * uniqueArray([ |
| 14 | + * { name: 'Tom', age: 20 }, |
| 15 | + * { name: 'Tom', age: 25 }, |
| 16 | + * { name: 'Jerry', age: 20 } |
| 17 | + * ], ['name']) // [{ name: 'Tom', age: 20 }, { name: 'Jerry', age: 20 }] |
| 18 | + * |
| 19 | + * // 按多个键去重 |
| 20 | + * uniqueArray([...], ['name', 'age']) // 只有name和age都相同才认为是重复项 |
| 21 | + * |
| 22 | + * // 按嵌套键去重 |
| 23 | + * uniqueArray([...], ['target.age']) // 支持深层嵌套访问 |
7 | 24 | */ |
8 | | -export function uniqueArray(array: any[]): any[] { |
| 25 | +export function uniqueArray(array: any[], keys?: string[]): any[] { |
9 | 26 | return array.reduce((result, item) => { |
10 | | - if ( |
11 | | - (isType(item, 'o|a') && !isHave(result, item)) |
12 | | - || !result.includes(item) |
13 | | - ) { |
14 | | - result.push(item) |
| 27 | + if (keys && keys.length > 0) { |
| 28 | + // 使用指定键进行比较 |
| 29 | + if (!isHaveByKeys(result, item, keys)) { |
| 30 | + result.push(item) |
| 31 | + } |
| 32 | + } |
| 33 | + else { |
| 34 | + // 原有逻辑 |
| 35 | + if ( |
| 36 | + (isType(item, 'o|a') && !isHave(result, item)) |
| 37 | + || !result.includes(item) |
| 38 | + ) { |
| 39 | + result.push(item) |
| 40 | + } |
15 | 41 | } |
16 | 42 | return result |
17 | 43 | }, []) |
18 | 44 | } |
19 | 45 |
|
20 | | -function equals(a: Record<any, any>, b: Record<any, any>): boolean { |
21 | | - if (Object.keys(a).length !== Object.keys(b).length) |
22 | | - return false |
23 | | - for (const key in a) { |
24 | | - if ( |
25 | | - (isType(a[key], 'o|a') |
26 | | - && isType(b[key], 'o|a') |
27 | | - && !equals(a[key], b[key])) |
28 | | - || a[key] !== b[key] |
29 | | - ) { |
| 46 | +function equals(a: any, b: any): boolean { |
| 47 | + if (a === b) |
| 48 | + return true |
| 49 | + |
| 50 | + if (a == null || b == null) |
| 51 | + return a === b |
| 52 | + |
| 53 | + if (Array.isArray(a) && Array.isArray(b)) { |
| 54 | + if (a.length !== b.length) |
30 | 55 | return false |
| 56 | + for (let i = 0; i < a.length; i++) { |
| 57 | + if (!equals(a[i], b[i])) |
| 58 | + return false |
31 | 59 | } |
| 60 | + return true |
32 | 61 | } |
33 | | - return true |
| 62 | + |
| 63 | + if (isType(a, 'o') && isType(b, 'o')) { |
| 64 | + const keysA = Object.keys(a) |
| 65 | + const keysB = Object.keys(b) |
| 66 | + if (keysA.length !== keysB.length) |
| 67 | + return false |
| 68 | + for (const key of keysA) { |
| 69 | + if (!keysB.includes(key) || !equals(a[key], b[key])) { |
| 70 | + return false |
| 71 | + } |
| 72 | + } |
| 73 | + return true |
| 74 | + } |
| 75 | + |
| 76 | + return false |
34 | 77 | } |
35 | 78 |
|
36 | 79 | function isHave(result: any[], item: any): boolean { |
37 | 80 | return result.some(i => isType(i, 'o|a') && equals(item, i)) |
38 | 81 | } |
| 82 | + |
| 83 | +function getValueByPath(obj: any, path: string): any { |
| 84 | + return path.split('.').reduce((current, key) => { |
| 85 | + return current && current[key] !== undefined ? current[key] : undefined |
| 86 | + }, obj) |
| 87 | +} |
| 88 | + |
| 89 | +function isHaveByKeys(result: any[], item: any, keys: string[]): boolean { |
| 90 | + return result.some((resultItem) => { |
| 91 | + return keys.every((key) => { |
| 92 | + const itemValue = getValueByPath(item, key) |
| 93 | + const resultItemValue = getValueByPath(resultItem, key) |
| 94 | + |
| 95 | + if (isType(itemValue, 'o|a') && isType(resultItemValue, 'o|a')) { |
| 96 | + return equals(itemValue, resultItemValue) |
| 97 | + } |
| 98 | + return itemValue === resultItemValue |
| 99 | + }) |
| 100 | + }) |
| 101 | +} |
0 commit comments