Skip to content

Commit eaa24ec

Browse files
committed
feat: enhance uniqueArray function to support deduplication by specified keys and add corresponding tests
1 parent fa0529b commit eaa24ec

File tree

2 files changed

+129
-18
lines changed

2 files changed

+129
-18
lines changed

src/array/uniqueArray.ts

Lines changed: 81 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,101 @@
11
import { isType } from '../is/isType'
22

33
/**
4-
*
4+
* 数组去重函数
55
* @param { any[] } array 数组
6+
* @param { string[] } keys 可选,指定比较的键路径数组,如 ['name', 'target.age']。如果指定了keys,则只要这些指定的键值都相同,就认为是同一项进行去重
67
* @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']) // 支持深层嵌套访问
724
*/
8-
export function uniqueArray(array: any[]): any[] {
25+
export function uniqueArray(array: any[], keys?: string[]): any[] {
926
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+
}
1541
}
1642
return result
1743
}, [])
1844
}
1945

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)
3055
return false
56+
for (let i = 0; i < a.length; i++) {
57+
if (!equals(a[i], b[i]))
58+
return false
3159
}
60+
return true
3261
}
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
3477
}
3578

3679
function isHave(result: any[], item: any): boolean {
3780
return result.some(i => isType(i, 'o|a') && equals(item, i))
3881
}
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+
}

test/array/uniqueArray.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,52 @@ describe('uniqueArray test', () => {
7272
]
7373
`)
7474
})
75+
76+
it('test with keys parameter', () => {
77+
// 测试按单个键去重
78+
const arrayWithName = [
79+
{ name: 'Tom', age: 20 },
80+
{ name: 'Tom', age: 25 },
81+
{ name: 'Jerry', age: 20 },
82+
]
83+
expect(uniqueArray(arrayWithName, ['name'])).toEqual([
84+
{ name: 'Tom', age: 20 },
85+
{ name: 'Jerry', age: 20 },
86+
])
87+
88+
// 测试按多个键去重
89+
const arrayWithMultipleKeys = [
90+
{ name: 'Tom', age: 20, city: 'New York' },
91+
{ name: 'Tom', age: 20, city: 'Los Angeles' },
92+
{ name: 'Tom', age: 25, city: 'New York' },
93+
{ name: 'Jerry', age: 20, city: 'New York' },
94+
]
95+
expect(uniqueArray(arrayWithMultipleKeys, ['name', 'age'])).toEqual([
96+
{ name: 'Tom', age: 20, city: 'New York' },
97+
{ name: 'Tom', age: 25, city: 'New York' },
98+
{ name: 'Jerry', age: 20, city: 'New York' },
99+
])
100+
101+
// 测试嵌套对象键
102+
const arrayWithNestedKeys = [
103+
{ name: 'Tom', target: { age: 20, skills: ['js', 'ts'] } },
104+
{ name: 'Tom', target: { age: 20, skills: ['js', 'ts'] } },
105+
{ name: 'Jerry', target: { age: 20, skills: ['python'] } },
106+
{ name: 'Bob', target: { age: 25, skills: ['js', 'ts'] } },
107+
]
108+
expect(uniqueArray(arrayWithNestedKeys, ['target.age'])).toEqual([
109+
{ name: 'Tom', target: { age: 20, skills: ['js', 'ts'] } },
110+
{ name: 'Bob', target: { age: 25, skills: ['js', 'ts'] } },
111+
])
112+
113+
// 测试嵌套对象完整比较
114+
expect(uniqueArray(arrayWithNestedKeys, ['target'])).toEqual([
115+
{ name: 'Tom', target: { age: 20, skills: ['js', 'ts'] } },
116+
{ name: 'Jerry', target: { age: 20, skills: ['python'] } },
117+
{ name: 'Bob', target: { age: 25, skills: ['js', 'ts'] } },
118+
])
119+
120+
// 测试空keys数组,应该使用原有逻辑
121+
expect(uniqueArray([1, 2, 2, 3], [])).toEqual([1, 2, 3])
122+
})
75123
})

0 commit comments

Comments
 (0)