公司动态
Harmony os 技术实战|拼豆制图38:4900 格色数统计如何从双重循环改成单次扫描
70×70 图纸有 4900 个格子色板固定 16 色。当前countColors()对每一种色遍历全部格子需要 16×490078400 次 ID 比较countBeads()又单独扫描 4900 次visibleColorCount()再扫 16 个统计项。单次并不慢但同样逻辑在资源仓库和图片转换服务各有一份批量构建 50 张图时会重复放大。本篇在不改变colorStats顺序和零计数项的前提下把统计改为“先建色板索引、再单次扫描格子、最后按色板顺序组装结果”同时计算拼豆总数与可见颜色数。优化重点不是炫耀大 O而是保持数据契约完全一致。一、先量化当前循环做了多少工作现有代码以色板为外层privatestaticcountColors(palette:BeadPaletteColor[],cells:BeadCell[]):BeadColorStat[]{conststats:BeadColorStat[][];for(leti0;ipalette.length;i){constcolorpalette[i];letcount0;for(letcellIndex0;cellIndexcells.length;cellIndex){if(cells[cellIndex].colorIdcolor.id)count;}stats.push({/* 元数据 */,count});}returnstats;}复杂度是O(P×C)P 为色板长度C 为格子数。当前 P16、C4900比较次数为 78400。50 张内置图全部初始化时上限约 392 万次比较还不包含格子创建和预览生成。这仍属于可接受规模但重复实现、重复扫描和未来色板扩容会让成本持续增长。二、输出契约比循环方式更重要优化前必须锁住四条行为colorStats.length 始终等于 palette.length colorStats 顺序始终与 palette 顺序一致 未使用颜色仍保留count 0 空格 colorIdempty 不计入任何色板颜色如果直接按格子遇到顺序向 Map 中插入统计项输出会只包含实际出现的颜色顺序也由图案扫描顺序决定。图例布局、色号展示和 16 色设定都会变化这不是等价优化。正确方案应改变计数过程不改变最终组装顺序。三、先构建 colorId 到数组下标的索引色板只有 16 项可用Mapstring, numberfunctionpaletteIndex(palette:BeadPaletteColor[]):Mapstring,number{constindexnewMapstring,number();for(leti0;ipalette.length;i){index.set(palette[i].id,i);}returnindex;}然后创建固定长度计数数组constcounts:number[][];for(leti0;ipalette.length;i){counts.push(0);}数组下标天然对应色板顺序Map 只负责把格子的colorId快速换成下标。若项目对 Map 使用有额外规则也可在调色板定义时把index写进颜色模型但不要在每个格子上重复搜索 16 项。四、一次格子扫描同时计算三类值定义统一结果interfacePatternStats{colorStats:BeadColorStat[];beadCount:number;visibleColorCount:number;}扫描阶段letbeadCount0;for(leti0;icells.length;i){constcellcells[i];if(cell.isEmpty)continue;beadCount;constpalettePositionindex.get(cell.colorId);if(palettePosition!undefined){counts[palettePosition];}}这一次循环替代原来的countColors()内层扫描和countBeads()。空格先跳过避免无意义 Map 查询。要注意“非空但 colorId 不在色板”这一异常beadCount是否仍加一取决于模型契约。当前isEmptyfalse就代表需要拼豆所以总数应加一同时记录未知色错误不能悄悄把它当空格。五、最后仍按色板顺序组装零计数项组装阶段constcolorStats:BeadColorStat[][];letvisibleColorCount0;for(leti0;ipalette.length;i){constcolorpalette[i];constcountcounts[i];if(count0)visibleColorCount;colorStats.push({colorId:color.id,colorCode:color.code,colorName:color.name,hex:color.hex,count});}最终复杂度为O(PC)16 次建索引、4900 次格子扫描、16 次结果组装。比较数量不再随P×C增长。更重要的是输出顺序、长度与元数据仍由 palette 决定页面不需要任何改动。六、不要把 colorStats.length 当成实际色数服务已经区分两种语义colorStats.length// 色板条目数通常 16visibleColorCount(colorStats)// count 0 的条目数单次统计可以直接返回visibleColorCount但仍不能把零计数项从colorStats删除。创建图案时应写conststatsbuildPatternStats(palette,chartCells);return{beadCount:stats.beadCount,colorCount:stats.visibleColorCount,colorStats:stats.colorStats};页面标题显示“使用了几色”应读colorCount固定 16 色图例若要展示全部色板则读colorStats如果图例只展示实际用色可在展示层筛选但不要改变底层统计契约。七、重复实现应该收敛到公共算法模块ImageConvertService和PatternRepository各有一份几乎相同的countColors()。两者的色板接口名称不同但字段兼容id、code、name、hex。可以定义公共最小接口exportinterfacePaletteEntry{id:string;code:string;name:string;hex:string;}exportfunctionbuildPatternStats(palette:PaletteEntry[],cells:BeadCell[]):PatternStats{// 单次统计实现}两个服务导入同一函数避免一处优化、一处仍保留旧逻辑。公共模块不能反向依赖页面或系统图片 API它应是可在本地测试中直接运行的纯算法。若两套色板未来语义不同也可保留适配器但计数核心仍只维护一份。八、重复 colorId 必须在入口拒绝Map 遇到重复 ID 时后一个会覆盖前一个index.set(palette[i].id,i);旧双重循环则会让两个相同 ID 的色板项都得到同样计数。两种行为都不合理色板 ID 应唯一。公共函数可在建索引时保护if(index.has(color.id)){thrownewError(duplicate palette id:${color.id});}index.set(color.id,i);同样应核对空 ID。只有输入不变量明确Map 优化才不会把脏数据悄悄归到错误下标。未知格子颜色可累计unknownColorIds开发阶段抛错发布环境回退到明确占位色或记录日志。不要让图例总数与拼豆总数无声不一致。九、基准要区分冷启动和单图转换单张 70×70 图从 78400 次比较降到约 4932 次主要循环工作理论上更省但真实耗时还包含图片解码、颜色距离、对象分配和 ArkUI 渲染统计未必是最大瓶颈。建议测量两类场景冷启动构建 50 张内置图案的总耗时与内存 单图转换PixelMap 解码结束后统计阶段单独耗时连续运行多轮并丢弃预热轮次固定同一图案和设备。日志只围绕算法入口与出口不要把系统相册对话框、文件 I/O 混进计时范围。优化价值还包括少一份重复代码和一次返回全部统计而不应只依据一次毫秒差异做结论。十、等价性回归比性能数字更优先用旧函数和新函数对同一批固定图案做对照expect(newStats.colorStats).assertDeepEquals(oldStats);expect(newStats.beadCount).assertEqual(oldBeadCount);expect(newStats.visibleColorCount).assertEqual(oldVisibleCount);边界样本包括4900 格全空beadCount0所有 count0可见色数0。全部为同一色对应 count4900可见色数1。16 色都出现统计顺序仍等于色板顺序。含未知 colorId 的非空格按约定报错或记录。色板有重复 ID入口明确失败。空色板 非空格不得假装统计成功。完成等价性后再比较耗时。若速度更快但零计数项消失页面图例已经改变不能视为正确优化。十一、结语颜色统计从O(P×C)改为O(PC)并不复杂为色板建一次 ID 索引单次扫描格子再按原色板顺序组装结果。真正需要谨慎的是守住零计数项、顺序、空格、未知颜色和可见色数这些既有语义。当同一段双重循环已经出现在资源仓库和图片转换服务中优化就不只是节省数万次比较更是一次算法边界收敛。让拼豆总数、实际色数和逐色统计来自同一次扫描页面上的三个数字才天然对应同一份格子快照。