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

超大型数组去重:保留最早时间戳的高效实现方案

大型数据集去重方案对比与实现脚本

背景

我有一个40万+行、4列的大型数据集,目前使用Tanaike提供的以下函数完成数据的批量读取和写入,运行效果良好,且已通过循环完成了部分行的清理操作(移除Content字段包含特定内容的行)。

function getValues_({ spreadsheetId, sheetName, start = 1, maxRow, limit = 100000 }) {
  return [...Array(Math.ceil(maxRow / limit))].flatMap((_) => {
    const last = start - 1 + limit;
    const range = `'${sheetName}'!A${start}:${last > maxRow ? maxRow : last}`;
    const temp = Sheets.Spreadsheets.Values.get(spreadsheetId, range).values;
    start += limit;
    return temp;
  });
}

function setValues_({ spreadsheetId, sheetName, start = 1, maxRow, limit = 100000, values }) {
  Array.from(Array(Math.ceil(maxRow / limit))).forEach((_) => {
    const v = values.splice(0, limit);
    Sheets.Spreadsheets.Values.update({ values: v }, spreadsheetId, `'${sheetName}'!A${start}`, { valueInputOption: "USER_ENTERED" });
    start += limit;
  });
}

去重需求

需基于ID、Content、Pathway三个字段判定重复项,保留Completion Date列中时间最早的行。

原始数据示例

ID内容(Content)完成日期(Completion Date)路径(Pathway)
1abc01/01/2024Apple
1def01/01/2024Apple
1ghi01/01/2024Apple
1def01/11/2024Apple
1abc01/01/2023Apple
1abc01/01/2024Apple

去重后目标结果

ID内容(Content)完成日期(Completion Date)路径(Pathway)
1def01/01/2024Apple
1ghi01/01/2024Apple
1abc01/01/2023Apple

候选方案对比

我考虑了两种实现方式,且已测试方式1处理40万行耗时约30秒,现分析优劣并提供对应实现:

方案1:写入表格后用removeDuplicates()去重

优劣分析

  • 优势:借助Google Sheets原生方法,代码实现简单,无需自行处理日期比较逻辑;原生方法经过官方优化,大型数据处理的稳定性有保障。
  • 劣势:需要额外的写入→排序→去重→读取步骤,存在多次表格IO操作,速度受网络或服务响应影响;需临时表存储中间数据,占用额外表格资源。

实现脚本

function deduplicateViaSheet(spreadsheetId, sheetName, tempSheetName = "TempDeduplicate") {
  const ss = SpreadsheetApp.openById(spreadsheetId);
  const sourceSheet = ss.getSheetByName(sheetName);
  const tempSheet = ss.getSheetByName(tempSheetName) || ss.insertSheet(tempSheetName);
  
  // 假设数据已在values数组中,写入临时表
  setValues_({
    spreadsheetId: spreadsheetId,
    sheetName: tempSheetName,
    start: 1,
    maxRow: values.length,
    values: values
  });
  
  // 按分组字段排序,确保最早日期的行排在每组首位
  tempSheet.getRange(1, 1, values.length, 4).sort([
    {column: 1, ascending: true},
    {column: 2, ascending: true},
    {column: 4, ascending: true},
    {column: 3, ascending: true}
  ]);
  
  // 基于ID、Content、Pathway去重,保留每组第一行(日期最早)
  tempSheet.getRange(1, 1, values.length, 4).removeDuplicates([1,2,4]);
  
  // 读取去重后的数据
  const deduplicatedValues = tempSheet.getDataRange().getValues();
  
  // 可选:删除临时表
  // ss.deleteSheet(tempSheet);
  
  return deduplicatedValues;
}

方案2:数组内直接去重后写入

优劣分析

  • 优势:全程在内存中处理,无额外表格IO操作,理论速度更快;无需临时表,节省资源。
  • 劣势:需自行实现日期解析、比较逻辑,代码复杂度略高;需注意内存占用(40万行4列数据在GAS内存限制范围内)。

高效实现脚本

function deduplicateInArray(values) {
  // 分离表头与数据行
  const header = values[0];
  const data = values.slice(1);
  
  // 用Map存储分组数据:key为ID+Content+Pathway的唯一组合,value为当前最早日期的行
  const groupMap = new Map();
  
  data.forEach(row => {
    const key = `${row[0]}|${row[1]}|${row[3]}`;
    // 解析日期,兼容MM/DD/YYYY格式
    const currentDate = Utilities.parseDate(row[2], Session.getScriptTimeZone(), "MM/dd/yyyy");
    
    if (!groupMap.has(key)) {
      groupMap.set(key, row);
    } else {
      // 对比已有行的日期,保留更早的
      const existingRow = groupMap.get(key);
      const existingDate = Utilities.parseDate(existingRow[2], Session.getScriptTimeZone(), "MM/dd/yyyy");
      if (currentDate < existingDate) {
        groupMap.set(key, row);
      }
    }
  });
  
  // 重组表头与去重后的数据
  return [header, ...Array.from(groupMap.values())];
}

// 使用示例:
// const cleanedValues = 已读取并完成初步清理的数组;
// const deduplicatedValues = deduplicateInArray(cleanedValues);
// setValues_({
//   spreadsheetId: "你的表格ID",
//   sheetName: "目标表名",
//   start: 1,
//   maxRow: deduplicatedValues.length,
//   values: deduplicatedValues
// });

更优方案建议

对于40万行数据,方案2的内存内去重通常比方案1更高效,减少了IO开销后,耗时一般可控制在10-20秒左右(取决于数据复杂度),优于方案1的30秒表现。

若原数据日期格式存在变体,需调整Utilities.parseDate的格式参数,确保日期解析准确。

内容的提问来源于stack exchange,提问作者DanCue

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 19:39:55