# 9989-统计数组中的元素个数
# 题目描述
实现 CountElementNumberToObject,统计数组里每个元素的出现次数,返回一个对象;嵌套数组要先拉平。
type A = CountElementNumberToObject<[]>; // {}
type B = CountElementNumberToObject<[1, 2, 3, 4, 5]>; // { 1: 1; 2: 1; 3: 1; 4: 1; 5: 1 }
type C = CountElementNumberToObject<[1, 2, 3, 4, 5, [1, 2, 3]]>; // { 1: 2; 2: 2; 3: 2; 4: 1; 5: 1 }
# 分析
两个小问题:
- Flatten:先把嵌套数组展平成一维。用递归 + 判断首项是否是数组即可。
- 计数:对展平后的元组,依次给结果对象的对应 key 累加 1。
关键点:对象的 value 从 未定义 递增到 N。需要在 mapped 层面迭代时,把当前 value + 1。TS 里没有原生加法,借助元组长度计数(见 加减乘除)。
# 题解
// 1. 先把多层嵌套数组拉平;同时跳过 never 项
type Flatten<T extends any[]> = T extends [infer F, ...infer R]
? [F] extends [never]
? Flatten<R>
: F extends any[]
? [...Flatten<F>, ...Flatten<R>]
: [F, ...Flatten<R>]
: [];
// 2. 把结果对象里某个 key 的值 +1
type Inc<N> = N extends number ? [...BuildTuple<N>, any]['length'] : never;
type BuildTuple<N, R extends any[] = []> = R['length'] extends N
? R
: BuildTuple<N, [...R, any]>;
// 3. 最后把累加器的链式交叉类型拍平成单一对象
type Merge<T> = { [K in keyof T]: T[K] };
type CountElementNumberToObject<
T extends any[],
Acc extends Record<any, any> = {},
> = Flatten<T> extends [infer F, ...infer R extends any[]]
? F extends keyof Acc
? CountElementNumberToObject<
R,
Omit<Acc, F & keyof Acc> & {
[K in F & PropertyKey]: Inc<Acc[F & keyof Acc]>;
}
>
: CountElementNumberToObject<R, Acc & { [K in F & PropertyKey]: 1 }>
: Merge<Acc>;
思路:
Flatten把嵌套数组拉平。每一项若是数组,先展开它再连上剩余;否则直接头插。注意never项要先跳过——否则never extends any[]会让整个条件短路成never,拖垮后续。- 每次把当前首项
F往累加器Acc里塞:- 如果
Acc已经有这个 key,把它的值 +1; - 否则新增字段,值为 1。
- 如果
- 用
BuildTuple实现"数字 +1":把当前值转成等长元组,push 一个占位再取length。 - 递归累积产生的是
{1:1} & {2:1} & ...形式的交叉类型,跟一个扁平对象字面量Equal并不相等;最后用Merge走一遍 mapped type 把结构拍平。
# 验证
type R1 = CountElementNumberToObject<[]>;
// {}
type R2 = CountElementNumberToObject<[1, 2, 3, 4, 5]>;
// { 1: 1; 2: 1; 3: 1; 4: 1; 5: 1 }
type R3 = CountElementNumberToObject<[1, 2, 3, 4, 5, [1, 2, 3]]>;
// { 1: 2; 2: 2; 3: 2; 4: 1; 5: 1 }
# 知识点
- 元组展平:递归 + 判断数组。
- 类型层 +1:借
BuildTuple,见 加减乘除。 - 对象累加:
Omit + &改写已有字段,&追加新字段。