You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 14:50:39