单词间隔重复算法

间隔重复算法

理论背景

遗忘曲线是一种描述记忆遗忘率的模型,艾宾浩斯在其著作《记忆:实验心理学的贡献》中首次详细描述了遗忘曲线,他使用了一些无意义的字母组合作为记忆对象,通过在不同的时间间隔后检查记忆的遗忘程度,得出了这一遗忘曲线。如图所示,就是典型的遗忘曲线。艾宾浩斯指出信息的遗忘速度随时间而非线性递减,遗忘最初非常迅速,然后逐渐放缓。基于这一发现,他提出了间隔效应(Spacing Effect),将复习活动分散到不同的日子进行,相比集中在一天内复习,可以显著减少所需的复习次数,并提高长期记忆的效率。这一发现对后续的研究产生了深远影响,启发了对间隔重复技术深入研究和应用的兴趣。

遗忘曲线

间隔重复(Spaced Repetition)是一种利用心理学间隔效应,通过不断复习所学内容并逐步增加复习期间的间隔来提升效率的学习方法。间隔重复方法适用于多种学习情境,特别是学习者需要牢固记忆大量知识的情形,比如外语词汇的学习,尤其是在目标语言的基础词汇数量很大的时候。将间隔重复应用到学习上的观点最初见于塞西尔·阿莱克·梅斯(Cecil Alec Mace)1932的著作《学习心理学》中,首次提出复习周期的概念:“Acts of revision should be spaced in gradually increasing intervals, roughly intervals of one day, two days, four days, eight days, and so on.” 在2009年的一篇名为《Spaced Repetition For Efficient Learning》的文章中,Gwern Branwen详细介绍了间隔重复技术在实现高效学习过程中的重要性。这篇文章讨论了间隔重复技术的原理和应用,旨在帮助学习者更有效地掌握知识和提高记忆效率,而非简单地死记硬背。
复习周期

算法设计

本系统基于赫尔曼·艾宾浩斯(Hermann Ebbinghaus)遗忘曲线公式实现简易间隔重复模型,使用指数衰减模型Math.exp(-x)根据用户标识的单词状态(已掌握、认识、模糊、忘记)来动态调整复习间隔,以提高用户的学习效率。材料以抽认卡的形式呈现,正面是单词,背面是释义。用户可以通过翻转抽认卡的方式来检查自己的记忆情况,并选择相应的记忆状态。

算法实现流程:

  1. 查询需要复习的单词:从数据库中查询到今日需要复习的单词列表。
  2. 状态评估:用户选择单词记忆的相应状态,如已掌握、认识、模糊、忘记。
  3. 间隔调整:根据单词的新状态,计算调整因子,并更新复习间隔。
  4. 更新数据库:将新的复习间隔和状态保存到数据库中,为下次复习做准备。

具体的流程如图所示。

间隔重复算法流程图

调整因子的计算基于以下公式:

factor = e^{-forgettingRate}

其中,遗忘率forgettingRate与用户选择的单词记忆状态相关联,状态越好,遗忘率越低,调整因子越大,复习间隔相应延长。

每个单词根据用户的记忆表现被分类为以下四种状态,并分配相应的遗忘率:

  • 忘记(Forgotten):遗忘率设置为 0.5,表示记忆较差,需要短时间内复习。
  • 模糊(Blurry):遗忘率设置为 0.3,表示记忆有一定基础,但不够牢固。
  • 熟悉(Known):遗忘率设置为 0.1,表示记忆相对稳定,可以延长复习间隔。
  • 掌握(Mastered):遗忘率为 0.0,表示单词已被完全掌握,无需进一步复习。

复习间隔的动态调整根据每个单词当前的复习间隔和计算得到的调整因子,动态调整下一次复习的时间。具体实现步骤如下:对于每个需要复习的单词,根据其当前状态查找其复习间隔。使用从状态对应的遗忘率计算出的调整因子来修改复习间隔。更新单词的复习间隔和预计下次复习时间。

这样通过公式计算后,调整因子factor的值在e^{-0.5}e^{-0.0}之间,即0.6065到1.0之间,再将其乘上默认的复习间隔"30,180,720,1440,2880,5760,10080,21600"(单位:分钟),即可得到新的复习间隔,并更新间隔索引。

对于计算用户的遗忘曲线,对数函数有助于模拟记忆衰减的减缓速度,具体计算公式如下:

forgettingRate = log((minutesUntilNextReview / FORGETTING_RATE_ADJUSTMENT) + 1)

其中,FORGETTING_RATE_ADJUSTMENT=30.0为遗忘率调整常量固定值,目前不考虑引入单词的难度级别或者是用户的记忆能力等因素。minutesSinceLastReview是距离上次复习以来的时间间隔,minutesUntilNextReview是距离下次复习的时间间隔,二者单位都为分钟。

最后,根据计算出的结果,通过以下公式计算出用户的记忆保留率:

retentionRate = e^(-forgetingRate * (minutesSinceLastReview / minutesUntilNextReview))

计算后将结果保存在数据库中,用于后续计算用户整体的遗忘曲线。

具体后端代码

间隔算法

controller
WordReviewController.java 调用服务层实现的方法对算法进行调整

package com.example.englishhub.controller;import com.example.englishhub.entity.LearningPlans;
import com.example.englishhub.entity.WordReview;
import com.example.englishhub.entity.WordReviewVO;
import com.example.englishhub.service.WordReviewService;
import com.example.englishhub.utils.Result;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import java.util.List;
import java.util.Map;/*** @Author: hahaha* @Date: 2024/4/8 15:13*/@RestController
@RequestMapping("/wordReview")
@Tag(name = "单词复习管理")
public class WordReviewController {@Autowiredprivate WordReviewService wordReviewService;/*** 调整复习* @body wordReview*/@Operation(summary = "调整复习")@PostMapping("/adjust")public Result adjustReviewIntervals(@RequestBody Map<String, Integer> body) {Integer wordId = body.get("wordId");Integer wordBookId = body.get("wordBookId");Integer status = body.get("status");
//    System.out.println("wordId = " + wordId);
//    System.out.println("wordBookId = " + wordBookId);
//    System.out.println("status = " + status);wordReviewService.adjustReviewIntervals(wordId, wordBookId, status);Result result = new Result();result.success("调整复习成功");return result;
}/*** 获取当天需学单词* @param wordBookId 单词书ID* @param dailyNewWords 每日新学单词数* @param dailyReviewWords 每日复习单词数* @return 复习单词列表*/@Operation(summary = "获取当天需学单词")@GetMapping("/getToday")public Result getToday(Integer wordBookId, Integer dailyNewWords, Integer dailyReviewWords) {Result result = new Result();List<WordReviewVO> wordReviews = wordReviewService.getWordsToday(wordBookId, dailyNewWords, dailyReviewWords);result.setData(wordReviews);result.success("获取当天复习单词成功");return result;}
}

service
WordReviewService.java 定义各个方法

package com.example.englishhub.service;import com.baomidou.mybatisplus.extension.service.IService;
import com.example.englishhub.entity.LearningPlans;
import com.example.englishhub.entity.WordReview;
import com.example.englishhub.entity.WordReviewVO;import java.util.List;/*** @Author: hahaha* @Date: 2024/4/8 15:14*/
public interface WordReviewService extends IService<WordReview> {void adjustReviewIntervals(Integer wordId, Integer wordBookId, Integer status);List<WordReviewVO> getWordsToday(Integer wordBookId, Integer dailyNewWords, Integer dailyReviewWords);List<WordReview> getAllWordsForUser(Integer userId);List<WordReview> getWordsByUserId();
}

mapper
WordReviewMapper.java

package com.example.englishhub.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.englishhub.entity.WordReview;
import com.example.englishhub.entity.WordReviewVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;import java.util.List;/*** <p>* 单词复习表 Mapper 接口* </p>* @Author: hahaha* @Date: 2024/4/8 15:14*/@Mapper
public interface WordReviewMapper extends BaseMapper<WordReview> {@Select("SELECT w.id, w.word, w.phonetic_uk, w.phonetic_us, w.definition," +" wd.audio_url, wd.video_url, wd.definition AS definitionDetail, wd.subtext" +" FROM word_relation wr" +" JOIN word w ON wr.word_id = w.id" +" JOIN words wd ON w.words_id = wd.id" +" LEFT JOIN word_review wrv ON wrv.word_id = w.id AND wrv.user_id = #{userId}" +" WHERE wr.word_book_id = #{wordBookId} AND wrv.id IS NULL" +" ORDER BY w.id ASC" +" LIMIT #{dailyNewWords}")List<WordReviewVO> getNewWordsToday(Integer userId, Integer wordBookId, Integer dailyNewWords);@Select("SELECT w.id, w.word, w.phonetic_uk, w.phonetic_us, w.definition," +" wd.audio_url, wd.video_url, wd.definition AS definitionDetail, wd.subtext" +" FROM word_review wr" +" JOIN word w ON wr.word_id = w.id" +" JOIN words wd ON w.words_id = wd.id" +" WHERE wr.user_id = #{userId} AND wr.word_book_id = #{wordBookId}" +" AND wr.next_review_time <= CURRENT_TIMESTAMP" +" ORDER BY wr.next_review_time ASC" +" LIMIT #{dailyReviewWords}")List<WordReviewVO> getReviewWordsToday(Integer userId, Integer wordBookId, Integer dailyReviewWords);
}

impl
WordReviewServiceImpl.java

package com.example.englishhub.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.englishhub.entity.ForgettingCurve;
import com.example.englishhub.entity.WordReview;
import com.example.englishhub.entity.WordReviewVO;
import com.example.englishhub.mapper.WordReviewMapper;
import com.example.englishhub.service.ForgettingCurveService;
import com.example.englishhub.service.LearningPlansService;
import com.example.englishhub.service.WordReviewService;
import com.example.englishhub.utils.JwtUtil;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.util.*;
import java.util.stream.Collectors;/*** @Author: hahaha* @Date: 2024/4/8 15:18*/@Service
public class WordReviewServiceImpl extends ServiceImpl<WordReviewMapper, WordReview> implements WordReviewService {@Autowiredprivate WordReviewMapper wordReviewMapper;@Autowiredprivate HttpServletRequest request;@Overridepublic void adjustReviewIntervals(Integer wordId, Integer wordBookId, Integer status) {String token = request.getHeader("token");String userId = JwtUtil.validateToken(token);QueryWrapper<WordReview> queryWrapper = new QueryWrapper<>();queryWrapper.eq("word_id", wordId);queryWrapper.eq("user_id", Integer.parseInt(userId));queryWrapper.eq("word_book_id", wordBookId);WordReview wordReview = this.getOne(queryWrapper);if (wordReview == null) {// 新学单词,使用默认的复习间隔并新增记录String defaultIntervals = "30,180,720,1440,2880,5760,10080,21600";List<Integer> intervals = Arrays.stream(defaultIntervals.split(",")).map(Integer::parseInt).collect(Collectors.toList());wordReview = new WordReview();wordReview.setUserId(Integer.parseInt(userId));wordReview.setWordId(wordId);wordReview.setWordBookId(wordBookId);wordReview.setStatus(status);wordReview.setReviewIntervals(defaultIntervals);wordReview.setReviewIntervalIndex(0);// 计算调整因子double factor = calculateAdjustmentFactor(status);// 调整复习间隔,将intervals列表中的每个元素乘以调整因子,然后将结果转换为整数。List<Integer> adjustedIntervals = intervals.stream().map(interval -> (int) (interval * factor)).collect(Collectors.toList());wordReview.setReviewIntervals(adjustedIntervals.stream().map(Object::toString).collect(Collectors.joining(",")));// 设置下次复习时间if (status == 4) { // 单词已掌握wordReview.setNextReviewTime(null);} else {int newIndex = 0;wordReview.setReviewIntervalIndex(newIndex);wordReview.setNextReviewTime(LocalDateTime.now().plusMinutes(adjustedIntervals.get(newIndex)));}this.save(wordReview);} else {List<Integer> intervals = Arrays.stream(wordReview.getReviewIntervals().split(",")).map(Integer::parseInt).collect(Collectors.toList());if (status == 4) { // 单词已掌握// 不再进行复习wordReview.setNextReviewTime(null);} else {double factor = calculateAdjustmentFactor(status);// 调整复习间隔,将intervals列表中的每个元素乘以调整因子,然后将结果转换为整数。List<Integer> adjustedIntervals = intervals.stream().map(interval -> (int) (interval * factor)).collect(Collectors.toList());// 更新复习间隔,将adjustedIntervals列表中的每个元素转换为字符串,然后使用逗号(,)将这些字符串连接起来。wordReview.setReviewIntervals(adjustedIntervals.stream().map(Object::toString).collect(Collectors.joining(",")));// 确保索引在合理范围内int newIndex = (wordReview.getReviewIntervalIndex() + 1) % adjustedIntervals.size();wordReview.setReviewIntervalIndex(newIndex);wordReview.setNextReviewTime(LocalDateTime.now().plusMinutes(adjustedIntervals.get(newIndex)));this.updateById(wordReview);}}}private WordReview getByWordId(Integer wordId) {QueryWrapper<WordReview> queryWrapper = new QueryWrapper<>();queryWrapper.eq("wordId", wordId);return this.getOne(queryWrapper);}@Overridepublic List<WordReviewVO> getWordsToday(Integer wordBookId, Integer dailyNewWords, Integer dailyReviewWords) {// 调用mapper层方法获取今天的所有复习记录String token = request.getHeader("token");String userId = JwtUtil.validateToken(token);List<WordReviewVO> reviewWords = wordReviewMapper.getReviewWordsToday(Integer.parseInt(userId), wordBookId, dailyReviewWords);List<WordReviewVO> newWords = wordReviewMapper.getNewWordsToday(Integer.parseInt(userId), wordBookId, dailyNewWords);reviewWords.addAll(newWords);return reviewWords;}@Overridepublic List<WordReview> getAllWordsForUser(Integer userId) {
//        String token = request.getHeader("token");
//        String userId = JwtUtil.validateToken(token);QueryWrapper<WordReview> queryWrapper = new QueryWrapper<>();queryWrapper.eq("userId", userId);return this.list(queryWrapper);}public List<WordReview> getWordsByUserId() {String token = request.getHeader("token");String userId = JwtUtil.validateToken(token);return getAllWordsForUser(Integer.parseInt(userId));}public double calculateAdjustmentFactor(int status) {double forgettingRate;switch (status) {case 1: // 忘记forgottenforgettingRate = 0.5;break;case 2: // 模糊 blurryforgettingRate = 0.3;break;case 3: // 熟悉 knownforgettingRate = 0.1;break;default: // 已掌握 masteredforgettingRate = 0.0;}// 使用指数函数来计算调整因子,遗忘率越大,调整因子越小// 原型是e^(-x)return Math.exp(-forgettingRate);}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/870563.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Android列表控件的属性与用法

列表控件的属性与用法 列表控件有Spinner、ListView、RecyclerView、ViewPager等。列表控件的显示一般涉及3个部分&#xff1a;控件、适配器、数据&#xff0c;这三者之间的关系如图1所示。适配器是数据与列表之间的桥梁&#xff0c;适配器中需要将数据中需要显示的属性与列表…

Python(四)---序列

文章目录 前言1.列表1.1.列表简介1.2.列表的创建1.2.1.基本方式[]1.2.2.list()方法1.2.3.range()创建整数列表1.2.4.推导式生成列表 1.3. 列表各种函数的使用1.3.1.增加元素1.3.2.删除元素1.3.3.元素的访问和计数1.3.4.切片1.3.5.列表的排序 1.4.二维列表 2.元组2.1.元组的简介…

智慧营区人员考勤管理系统|DW-S406系统实现无感考勤

智慧营区人员管理系统&#xff08;DW-S406系统&#xff09;通过建设人员基本信息管理功能&#xff0c;实现人力资源可视化、规范化管理&#xff0c;使人力资源管理工作决策的高效化、制度化得到有力保障&#xff0c;真正达到集中管理、集权管理的目标。主要实现营区人员管理、访…

对接高德开放平台API

高德开放平台API&#xff1a; https://lbs.amap.com/ 一、天气查询 天气查询: https://lbs.amap.com/api/webservice/guide/api/weatherinfo adcode城市码表下载: https://lbs.amap.com/api/webservice/download Component public class WeatherUtil {Resourceprivate GdCon…

【实践分享】深度学习远程连接GPU

目录 前言 一、创建实例 二、上传文件 三、服务器上传 四、运行代码文件 经验之谈 前言 1、使用平台&#xff1a;恒源云 2、教程总结自B站大佬Larry同学发布的教程视频 一、创建实例 通俗&#xff1a;租用一台临时的电脑&#xff0c;电脑可自选GPU型号等&#xff0c;…

MyBatis框架学习笔记(三):MyBatis重要文件详解:配置文件与映射文件

1 mybatis-config.xml-配置文件详解 1.1 说明 &#xff08;1&#xff09;mybatis 的核心配置文件(mybatis-config.xml)&#xff0c;比如配置 jdbc 连接信息&#xff0c;注册 mapper 等等都是在这个文件中进行配置,我们需要对这个配置文件有详细的了解 &#xff08;2&#x…

这些免费看电视的直播软件,还能免费追剧的app,需要的收藏!

想看中央台和地方卫视的电视直播app有什么呢&#xff1f;支持手机和智能电视的电视直播软件有哪些&#xff1f;今天要跟大家聊聊那些让人眼前一亮的电视直播软件&#xff0c;特别是2024年还能免费看电视直播的神器&#xff0c;让家里的老人也能享受到精彩的电视节目&#xff0c…

复杂度(上卷)

前言 在正式进入今天的主题之前&#xff0c;我们不妨先来回顾一下初步学习数据结构后必须知道的概念。&#x1f3b6; 数据结构 数据结构是计算机存储、组织数据的方式&#xff0c;指相互间存在一种或多种特定关系的数据元素的集合。 &#xff08;没有一种单一的数据结构能够…

ServiceDesk Plus再次获得国际认可的粉象认证

我们又一次做到了&#xff01;ServiceDesk Plus 现已获得 CMDB 和发布部署过程的 PinkVERIFY™ &#xff08;粉象&#xff09;认证。 通过PinkVerify 认证&#xff0c;我们现在已经获得了七项核心 IT 服务管理实践&#xff1a; 1、事件管理 2、问题管理 3、变更管理 4、资产管…

探索 Electron:窗口菜单以及生命周期和对话框讲解

Electron是一个开源的桌面应用程序开发框架&#xff0c;它允许开发者使用Web技术&#xff08;如 HTML、CSS 和 JavaScript&#xff09;构建跨平台的桌面应用程序&#xff0c;它的出现极大地简化了桌面应用程序的开发流程&#xff0c;让更多的开发者能够利用已有的 Web 开发技能…

Python类与对象01

1、理解使用对象完成数据组织的思路 1.1类和对象的基本理解 理解类&#xff1a;从现实世界到编程世界 类由三个部分组成&#xff1a;类名、类的属性、类的方法。类的定义实际上是描述事物的一种方法&#xff0c;在现实世界中&#xff0c;事物都是有属性和行为的。通过类&…

JVM:类加载器

文章目录 一、什么是类加载器二、类加载器的应用场景三、类加载器的分类1、分类2、启动类加载器 四、双亲委派机制五、打破双亲委派机制六、JDK9之后的类加载器 一、什么是类加载器 类加载器&#xff08;ClassLoader&#xff09;是Java虚拟机提供给应用程序去实现获取类和接口…

休息时间c++

题目描述 小杨计划在某个时刻开始学习&#xff0c;并决定在学习k秒后开始休息。 小杨想知道自己开始休息的时刻是多少。 输入 前三行每行包含一个整数&#xff0c;分别表示小杨开始学习时刻的时h、分m、秒s(h&#xff0c;m&#xff0c;s的值符合1≤h≤12,0≤m≤59,0≤s≤59)…

Geoserver源码解读六 插件

系列文章目录 Geoserver源码解读一 环境搭建 Geoserver源码解读二 主入口 Geoserver源码解读三 GeoServerBasePage Geoserver源码解读四 REST服务 Geoserver源码解读五 Catalog Geoserver源码解读六 插件&#xff08;怎么在开发模式下使用&#xff09; 目录 系列文章目…

看番工具 -- oneAnime v1.2.5绿色版

软件简介 OneAnime是一款专为动漫爱好者设计的应用程序&#xff0c;它提供了一个庞大的动漫资源库&#xff0c;用户可以在这里找到各种类型的动漫&#xff0c;包括热门的、经典的、新番的等等。OneAnime的界面设计简洁明了&#xff0c;操作方便&#xff0c;用户可以轻松地搜索…

C++系列-Vector(一)

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” Vector的介绍及使用 Vector的介绍 当vector构建的参数类型为char类型时&#xff0c;它是和string是极其类似的&#xff0c;但是二者之间也有不同&#xff0c;比如&#xff0c…

[C++] 模拟实现list(二)

标题&#xff1a;[C] 模拟实现list&#xff08;二&#xff09; 水墨不写bug 目录 &#xff08;一&#xff09;回顾 &#xff08;二&#xff09;迭代器类的封装设计 &#xff08;1&#xff09;成员函数简要分析 &#xff08;2&#xff09;const迭代器类的设计 &#xff08;…

二四、3d人脸构建

一、下载github项目3dmm_cnn-master https://github.com/anhttran/3dmm_cnn.git 一个使用深度神经网络从单个图像进行 3D 人脸建模的项目,端到端代码,可直接根据图像强度进行 3D 形状和纹理估计;使用回归的 3D 面部模型,从检测到的面部特征点估计头部姿势和表情。…

19185 01背包问题

解决这个问题的关键是使用动态规划的方法。我们可以创建一个二维数组dp[i][j]&#xff0c;其中i表示考虑前i件物品&#xff0c;j表示背包的容量。dp[i][j]的值表示在考虑前i件物品&#xff0c;且背包容量为j时能获得的最大价值。 ### 算法步骤 1. 初始化一个二维数组dp&#x…

机器学习(五) -- 监督学习(7) --SVM2

系列文章目录及链接 上篇&#xff1a;机器学习&#xff08;五&#xff09; -- 监督学习&#xff08;7&#xff09; --SVM1 下篇&#xff1a; 前言 tips&#xff1a;标题前有“***”的内容为补充内容&#xff0c;是给好奇心重的宝宝看的&#xff0c;可自行跳过。文章内容被“文…