如何在TypeScript中实现松耦合的统计聚合函数
实现松耦合的对象属性聚合函数
我当前实现了一个基于Map的数组对象聚合统计函数,用于按player_id分组,对数值属性求和、字符串属性保留最后出现的值,但函数和具体属性强耦合,新增属性必须修改函数代码:
const aggregateStats = (dataframe: IBasicOffensePlayerStats[]) => { let teamsMap = new Map(); for (let obj in dataframe) { if (teamsMap.get(dataframe[obj].player_id)) { let currentObj = teamsMap.get(dataframe[obj].player_id); let newObj = { player_id: dataframe[obj].player_id, week_count: Number.parseInt(currentObj.week_count.toString()) + Number.parseInt(dataframe[obj].week_count.toString()), pass_attempt: Number.parseInt(currentObj.pass_attempt.toString()) + Number.parseInt(dataframe[obj].pass_attempt.toString()), completion: Number.parseInt(currentObj.completion.toString()) + Number.parseInt(dataframe[obj].completion.toString()), incompletion: Number.parseInt(currentObj.incompletion.toString()) + Number.parseInt(dataframe[obj].incompletion.toString()), game_id_db: currentObj.game_id_db, team_abbr: currentObj.team_abbr, position: currentObj.position, }; teamsMap.set(currentObj.player_id, newObj); } else { teamsMap.set(dataframe[obj].player_id, { ...dataframe[obj], }); } } return Array.from(teamsMap.values()); };
对应的接口定义:
export interface IBasicOffensePlayerStats { player_id: string; position: string; game_id_db: string; pass_attempt: number; completion: number; incompletion: number; week_count: number; }
示例输入数据:
const playerData: IBasicOffensePlayerStats[] = [ {player_id: "L.Jackson", position: "QB", game_id_db: "2022_01_BAL_NYJ", pass_attempt: 28, completion: 18, incompletion: 10, week_count: 1}, {player_id: "L.Jackson", position: "QB", game_id_db: "2022_02_BAL_MIA", pass_attempt: 31, completion: 20, incompletion: 11, week_count: 1}, {player_id: "J.Burrow", position: "QB", game_id_db: "2022_03_NYJ_CIN", pass_attempt: 43, completion: 28, incompletion: 15, week_count: 1} ]
函数返回结果:
[ {player_id: "L.Jackson", position: "QB", game_id_db: "2022_02_BAL_MIA", pass_attempt: 59, completion: 38, incompletion: 21, week_count: 2}, {player_id: "J.Burrow", position: "QB", game_id_db: "2022_03_NYJ_CIN", pass_attempt: 43, completion: 28, incompletion: 15, week_count: 1} ]
现在想知道:能不能实现松耦合的函数,新增passing_yards这类数值属性或其他字符串属性时,不用修改函数代码?
解决方案
当然可以,我们可以通过泛型+动态属性遍历的方式实现松耦合,核心思路是:
- 用泛型支持任意结构的输入对象
- 允许传入分组键(比如
player_id),让函数更通用 - 自动判断属性类型:数值类型求和,非数值类型保留最后出现的值
- 移除冗余的类型转换(原函数里的
parseInt完全没必要,因为属性本身就是number类型)
改进后的函数代码:
function aggregateStats<T extends Record<string, any>>( data: T[], groupKey: keyof T ): T[] { const groupMap = new Map<T[keyof T], T>(); for (const item of data) { const key = item[groupKey]; const existing = groupMap.get(key); if (existing) { // 遍历所有属性,动态处理聚合 const aggregated = { ...existing }; for (const prop in item) { if (typeof aggregated[prop] === 'number' && typeof item[prop] === 'number') { aggregated[prop] += item[prop]; } else { // 非数值属性用最新值覆盖 aggregated[prop] = item[prop]; } } groupMap.set(key, aggregated as T); } else { groupMap.set(key, { ...item }); } } return Array.from(groupMap.values()); }
用法示例
1. 原数据场景
直接传入数据和分组键player_id即可,结果和原函数一致:
const result = aggregateStats(playerData, 'player_id'); console.log(result);
2. 新增属性场景
比如给接口新增passing_yards数值属性和team_abbr字符串属性:
export interface IExtendedOffensePlayerStats extends IBasicOffensePlayerStats { passing_yards: number; team_abbr: string; } const extendedPlayerData: IExtendedOffensePlayerStats[] = [ {player_id: "L.Jackson", position: "QB", game_id_db: "2022_01_BAL_NYJ", pass_attempt: 28, completion: 18, incompletion: 10, week_count: 1, passing_yards: 250, team_abbr: "BAL"}, {player_id: "L.Jackson", position: "QB", game_id_db: "2022_02_BAL_MIA", pass_attempt: 31, completion: 20, incompletion: 11, week_count: 1, passing_yards: 320, team_abbr: "BAL"}, {player_id: "J.Burrow", position: "QB", game_id_db: "2022_03_NYJ_CIN", pass_attempt: 43, completion: 28, incompletion: 15, week_count: 1, passing_yards: 380, team_abbr: "CIN"} ]; const extendedResult = aggregateStats(extendedPlayerData, 'player_id'); console.log(extendedResult);
返回结果中,passing_yards会自动求和(250+320=570),team_abbr保留最后值(这里两个都是BAL,结果不变),完全不需要修改聚合函数。
注意点
- 如果属性值可能为
null/undefined,可以在类型判断里增加处理逻辑,比如跳过空值 - 若需要区分不同的聚合逻辑(比如平均值、最大值),可以扩展函数传入聚合规则配置,但当前实现已满足你的核心需求
内容的提问来源于stack exchange,提问作者jwald3
相关产品推荐
相关产品推荐

