超大型数组去重:保留最早时间戳的高效实现方案
大型数据集去重方案对比与实现脚本
背景
我有一个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) |
|---|---|---|---|
| 1 | abc | 01/01/2024 | Apple |
| 1 | def | 01/01/2024 | Apple |
| 1 | ghi | 01/01/2024 | Apple |
| 1 | def | 01/11/2024 | Apple |
| 1 | abc | 01/01/2023 | Apple |
| 1 | abc | 01/01/2024 | Apple |
去重后目标结果
| ID | 内容(Content) | 完成日期(Completion Date) | 路径(Pathway) |
|---|---|---|---|
| 1 | def | 01/01/2024 | Apple |
| 1 | ghi | 01/01/2024 | Apple |
| 1 | abc | 01/01/2023 | Apple |
候选方案对比
我考虑了两种实现方式,且已测试方式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
相关产品推荐
相关产品推荐

