HarmonyOS 应用开发《掌上英语》第27篇:学习统计持久化——StatisticsManager 的数据聚合方案
学习统计持久化——StatisticsManager 的数据聚合方案引言学习数据统计是教育类 App 的核心功能之一。用户完成一次练习后系统需要快速计算出正确率、各题型得分率、平均答题时长等指标并将这些数据持久化以便用户在学习报告等页面查看历史趋势。本文将以StatisticsManager的实现为例分析如何设计一套可扩展、可聚合的学习统计方案。一、数据模型设计统计数据的核心是PracticeStatistics接口它包含了全局统计和分题型统计两个维度exportinterfacePracticeStatistics{// 全局统计数据totalQuestions:number// 总答题数correctQuestions:number// 正确题数wrongQuestions:number// 错误题数correctRate:number// 正确率百分比vocabularyMasteryRate:number// 词汇掌握率listeningCorrectRate:number// 听力正确率readingCorrectRate:number// 阅读正确率clozeCorrectRate:number// 完形填空正确率avgAnswerTime:number// 平均答题时长秒// 分题型统计typeStatistics:TypeStat[]}exportinterfaceTypeStat{type:string// 题型编号1词汇 2听力 3阅读 4完形 5语法 6口语total:number// 该题型总题数correct:number// 正确数wrong:number// 错误数}设计亮点typeStatistics数组而非固定字段。这样可以支持未来新增题型而无需修改数据模型——符合开闭原则。二、单次练习统计计算calculateStatistics方法接收一次练习的题目列表和总时长实时计算各项指标publiccalculateStatistics(ques:TopicItemType[],duration:number):PracticeStatistics{try{lettotalQuestionsques.length;letcorrectQuestions0;letwrongQuestions0;lettypeStats:TypeStat[][];ques.forEach(item{if(item.isAnswer){// 判断答案是否正确letisCorrectthis.checkAnswerCorrect(item.selectQues,item.rightQues);if(isCorrect){correctQuestions;}else{wrongQuestions;}// 按题型累加统计lettypeKeyitem.type||其他;letfoundfalse;for(leti0;itypeStats.length;i){if(typeStats[i].typetypeKey){typeStats[i].total;if(isCorrect)typeStats[i].correct;elsetypeStats[i].wrong;foundtrue;break;}}if(!found){typeStats.push({type:typeKey,total:1,correct:isCorrect?1:0,wrong:isCorrect?0:1});}}});letcorrectRatetotalQuestions0?(correctQuestions/totalQuestions*100):0;// 各题型专项正确率letlisteningCorrectRatethis.getTypeCorrectRate(typeStats,2);letreadingCorrectRatethis.getTypeCorrectRate(typeStats,3);letclozeCorrectRatethis.getTypeCorrectRate(typeStats,4);letvocabularyMasteryRatethis.getTypeCorrectRate(typeStats,1);letavgAnswerTimetotalQuestions0?Math.floor(duration/totalQuestions):0;letstatistics:PracticeStatistics{totalQuestions,correctQuestions,wrongQuestions,correctRate:Math.round(correctRate*100)/100,vocabularyMasteryRate:Math.round(vocabularyMasteryRate*100)/100,listeningCorrectRate:Math.round(listeningCorrectRate*100)/100,readingCorrectRate:Math.round(readingCorrectRate*100)/100,clozeCorrectRate:Math.round(clozeCorrectRate*100)/100,avgAnswerTime,typeStatistics:typeStats};// 合并到历史数据this.saveStatistics(statistics);returnstatistics;}catch(e){Logger.error(StatisticsManager,计算统计数据失败:${JSON.stringify(e)});returnthis.getEmptyStatistics();}}核心逻辑遍历题目逐题判断是否正确同时按题型分类统计。答案比较checkAnswerCorrect比较selectQues和rightQues两个数组排序后是否完全一致。四舍五入使用Math.round(x * 100) / 100保留两位小数。降级处理异常时返回空统计数据不影响用户使用。三、历史数据聚合单次练习的数据需要与历史数据合并才能反映用户的长期学习趋势privatesaveStatistics(statistics:PracticeStatistics):void{try{lethistoryStatsthis.getHistoryStatistics();letmergedStats:PracticeStatistics{totalQuestions:historyStats.totalQuestionsstatistics.totalQuestions,correctQuestions:historyStats.correctQuestionsstatistics.correctQuestions,wrongQuestions:historyStats.wrongQuestionsstatistics.wrongQuestions,correctRate:statistics.totalQuestions0?statistics.correctRate:historyStats.correctRate,vocabularyMasteryRate:statistics.vocabularyMasteryRate,listeningCorrectRate:statistics.listeningCorrectRate,readingCorrectRate:statistics.readingCorrectRate,clozeCorrectRate:statistics.clozeCorrectRate,avgAnswerTime:statistics.avgAnswerTime,typeStatistics:this.mergeTypeStatistics(historyStats.typeStatistics,statistics.typeStatistics)};PreferenceUtil.getInstance().put(this.STATISTICS_KEY,mergedStats);}catch(e){Logger.error(StatisticsManager,保存统计数据失败:${JSON.stringify(e)});}}聚合策略累加型字段totalQuestions、correctQuestions、wrongQuestions历史值 新值直接相加。覆盖型字段correctRate、各专项正确率以最新一次计算为准。分题型统计通过mergeTypeStatistics方法进行深度合并。privatemergeTypeStatistics(history:TypeStat[],current:TypeStat[]):TypeStat[]{// 先深度复制历史数据letresult:TypeStat[][];for(leti0;ihistory.length;i){result.push({type:history[i].type,total:history[i].total,correct:history[i].correct,wrong:history[i].wrong});}// 合并当前数据for(leti0;icurrent.length;i){letfoundfalse;for(letj0;jresult.length;j){if(result[j].typecurrent[i].type){result[j].totalcurrent[i].total;result[j].correctcurrent[i].correct;result[j].wrongcurrent[i].wrong;foundtrue;break;}}if(!found){result.push({type:current[i].type,total:current[i].total,correct:current[i].correct,wrong:current[i].wrong});}}returnresult;}这里手动深度复制而非直接赋值是因为TypeStat是引用类型直接复制会共享底层对象导致历史数据被意外修改。四、历史数据读取与展示publicgetHistoryStatistics():PracticeStatistics{try{letstatsPreferenceUtil.getInstance().get(this.STATISTICS_KEY,undefined)asPracticeStatistics;if(!stats){returnthis.getEmptyStatistics();}returnstats;}catch(e){Logger.error(StatisticsManager,获取历史统计数据失败:${JSON.stringify(e)});returnthis.getEmptyStatistics();}}publicgetQuestionTypeStats():QuestionTypeStat[]{try{letstatsthis.getHistoryStatistics();letresult:QuestionTypeStat[][];for(leti0;istats.typeStatistics.length;i){lettypeStatstats.typeStatistics[i];result.push({typeName:this.getTypeName(typeStat.type),total:typeStat.total,correct:typeStat.correct,wrong:typeStat.wrong,correctRate:typeStat.total0?Math.round(typeStat.correct/typeStat.total*10000)/100:0});}returnresult.sort((a,b)b.total-a.total);}catch(e){Logger.error(StatisticsManager,获取题型统计失败:${JSON.stringify(e)});return[];}}getQuestionTypeStats将内部的数字题型编号转换为可读的中文名称并按总题数降序排列方便 UI 层展示。五、最佳实践5.1 空值安全首次使用或数据被清空时getHistoryStatistics返回空统计数据而非null避免调用方做空值检查privategetEmptyStatistics():PracticeStatistics{return{totalQuestions:0,correctQuestions:0,wrongQuestions:0,correctRate:0,vocabularyMasteryRate:0,listeningCorrectRate:0,readingCorrectRate:0,clozeCorrectRate:0,avgAnswerTime:0,typeStatistics:[]};}5.2 精度控制所有百分比值使用Math.round(x * 100) / 100确保保留两位小数避免浮点数精度问题导致 UI 上显示一长串小数。5.3 题型映射集中管理题型编号到中文名称的映射集中在getTypeName方法中避免在 UI 层散落 switch-caseprivategetTypeName(type:string):string{switch(type){case1:return单词拼写case2:return听力理解case3:return阅读理解case4:return完形填空case5:return语法练习case6:return口语练习default:return题型${type}}}六、总结StatisticsManager展现了一个典型的数据聚合场景从原始数据题目列表到计算指标正确率再到历史数据合并累加 覆盖最后到 UI 友好的展示格式题型名称转换 排序。这种分层设计使得统计逻辑高度内聚、易于测试同时在需要新增题型或统计维度时只需扩展TypeStat数组即可无需修改核心聚合逻辑。

相关新闻

Grok智能体后台模式详解:AI自动化任务的长时运行实践

Grok智能体后台模式详解:AI自动化任务的长时运行实践

今天来看一个值得关注的技术更新:Grok智能体现在支持后台模式运行。这个功能对于需要长时间运行AI任务的用户来说是个重要改进,意味着你可以让智能体在后台持续处理任务,而不需要保持终端窗口打开。Grok智能体是由xAI开发的AI执行系统&#x…

2026/10/14 1:47:28 阅读更多 →
第五章WSaiOS 多模态融合感知引擎实现

第五章WSaiOS 多模态融合感知引擎实现

第五章WSaiOS 多模态融合感知引擎实现WSaiOS Multimodal Fusion Perception Engine——构建统一世界感知数据入口5.1 多模态融合感知工程目标WSaiOS 多模态融合感知引擎的目标:将不同来源的数据:图像;视频;文本;音频&a…

2026/10/14 1:49:20 阅读更多 →
大模型时代程序员转型指南:从传统开发到AI工程化

大模型时代程序员转型指南:从传统开发到AI工程化

1. 大模型技术如何重塑程序员就业市场去年面试Java开发岗时,面试官突然问我是否了解LangChain框架,这个细节让我意识到技术风向正在发生根本性转变。现在打开任何招聘平台,算法工程师岗位JD中"熟悉大模型原理"几乎成为标配&#xf…

2026/10/9 6:59:50 阅读更多 →

最新新闻

CDEGS变电站接地网建模与计算全流程避坑指南

CDEGS变电站接地网建模与计算全流程避坑指南

简介:CDEGS软件学习资料包面向电力系统、输电线路、地下设施及电磁环境分析方向的工程师与学生,系统涵盖从电磁场基础理论到工程仿真的完整链路,可帮助用户系统建立CDEGS仿真分析能力。压缩包内含15461个文件,约976MB,…

2026/10/14 1:49:15 阅读更多 →
从复位向量到main:STM32启动流程寄存器级拆解

从复位向量到main:STM32启动流程寄存器级拆解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 1:49:15 阅读更多 →
Windows蓝屏错误码速查与排查指南:从0x0000到0x0427

Windows蓝屏错误码速查与排查指南:从0x0000到0x0427

简介:这份电脑蓝屏死机代码大集合面向经常遭遇Windows蓝屏、却难以判断错误来源的普通用户与初级运维人员,帮助其在系统崩溃时快速定位故障类型,减少盲目重装系统的困扰。资源以doc文档形式整理,压缩包内共1个文件,体积…

2026/10/14 1:49:15 阅读更多 →
民航ETERM指令培训:黑屏订座、PNR操作与出票避坑指南

民航ETERM指令培训:黑屏订座、PNR操作与出票避坑指南

简介:ETERM指令培训是一份面向航空公司票务、机场地面服务及民航相关专业初学者的入门实操文档。内容围绕中航信ETERM终端系统的核心指令展开,从航班可用性查询、座位预定、PNR建立与封口,到出票时限设置、电子客票提取、里程录入&#xff0c…

2026/10/14 1:49:15 阅读更多 →
软件模块化设计核心:抽象、接口与契约实践指南

软件模块化设计核心:抽象、接口与契约实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 1:49:15 阅读更多 →
AI Agent构建指南:简单可组合模式比复杂框架更有效

AI Agent构建指南:简单可组合模式比复杂框架更有效

简介:Claude官方发布的《构建高效的Agents》全文翻译中文版,系统梳理了Anthropic团队与数十个行业团队合作一年的核心经验:有效的智能代理并不依赖复杂框架,而应采用简单、可组合的设计模式;报告还从词源学角度厘清代理…

2026/10/14 1:48:14 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →