Python开发英语记忆单词软件

"""
英语单词记忆软件 - 基于记忆曲线的间隔重复学习系统
支持Markdown格式单词本,手动控制释义显示,自动安排复习计划
"""

import sqlite3
import datetime
import re
import os
import random
from tkinter import *
from tkinter import messagebox, filedialog
from tkinter import ttk


class WordMemoryDB:
    """数据库管理类,处理单词存储和记忆曲线算法"""
    
    def __init__(self, db_path='words.db'):
        self.db_path = db_path
        self.init_database()
    
    def init_database(self):
        """初始化数据库表结构"""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        cursor.execute('''
            CREATE TABLE IF NOT EXISTS words (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                word TEXT NOT NULL UNIQUE,  -- 添加UNIQUE约束防止重复
                meaning TEXT,
                example TEXT,
                repetitions INTEGER DEFAULT 0,
                ease_factor REAL DEFAULT 2.5,
                interval_days INTEGER DEFAULT 0,
                last_review DATE,
                next_review DATE
            )
        ''')
        conn.commit()
        conn.close()
    
    def import_from_markdown(self, md_path):
        """
        从Markdown文件导入单词(仅添加新词,不清空原有数据)
        预期格式:每个词条由三行组成(单词、中文释义、例句),词条间用空行分隔
        返回新增的单词数量
        """
        if not os.path.exists(md_path):
            raise FileNotFoundError(f"单词本文件不存在: {md_path}")
        
        with open(md_path, 'r', encoding='utf-8') as f:
            lines = f.readlines()
        
        # 清理空行和Markdown标记
        cleaned_lines = []
        for line in lines:
            line = line.strip()
            if line:  # 跳过空行
                # 移除常见的Markdown标记
                line = re.sub(r'\*\*(.*?)\*\*', r'\1', line)  # 粗体
                line = re.sub(r'\*(.*?)\*', r'\1', line)      # 斜体
                line = re.sub(r'#+', '', line)                # 标题标记
                line = line.strip()
                cleaned_lines.append(line)
        
        # 每三行组成一个词条
        words_data = []
        for i in range(0, len(cleaned_lines), 3):
            if i + 2 < len(cleaned_lines):
                word = cleaned_lines[i]
                meaning = cleaned_lines[i+1]
                example = cleaned_lines[i+2]
                words_data.append((word, meaning, example))
        
        # 获取数据库中已存在的单词(用于去重)
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        cursor.execute("SELECT word FROM words")
        existing_words = {row[0] for row in cursor.fetchall()}
        
        # 插入新单词(去重)
        today = datetime.date.today().isoformat()
        new_count = 0
        for word, meaning, example in words_data:
            if word not in existing_words:
                try:
                    cursor.execute('''
                        INSERT INTO words (word, meaning, example, last_review, next_review)
                        VALUES (?, ?, ?, ?, ?)
                    ''', (word, meaning, example, today, today))
                    new_count += 1
                    existing_words.add(word)  # 更新已存在集合,避免本次导入中的重复
                except sqlite3.IntegrityError:
                    # 如果因为并发等原因仍冲突,跳过
                    pass
        
        conn.commit()
        conn.close()
        return new_count
    
    def get_due_words(self, exclude_ids=None):
        """
        获取今天需要复习的单词
        exclude_ids: 已在本轮复习过的ID集合
        """
        if exclude_ids is None:
            exclude_ids = set()
        
        today = datetime.date.today().isoformat()
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        # 构建排除条件
        placeholders = ','.join(['?'] * len(exclude_ids)) if exclude_ids else ''
        query = f'''
            SELECT id, word, meaning, example, repetitions, ease_factor, interval_days
            FROM words
            WHERE next_review <= ? AND id NOT IN ({placeholders})
            ORDER BY random()
            LIMIT 1
        '''
        
        params = [today] + list(exclude_ids) if exclude_ids else [today]
        cursor.execute(query, params)
        result = cursor.fetchone()
        conn.close()
        return result
    
    def update_word_review(self, word_id, quality):
        """
        根据用户反馈更新单词记忆参数(SM-2算法简化版)
        quality: 0=忘记/错误, 1=模糊/困难, 2=正确/记得
        """
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        # 获取当前单词数据
        cursor.execute('''
            SELECT repetitions, ease_factor, interval_days
            FROM words WHERE id = ?
        ''', (word_id,))
        row = cursor.fetchone()
        if not row:
            conn.close()
            return
        
        repetitions, ease_factor, interval_days = row
        today = datetime.date.today()
        
        if quality >= 2:  # 正确
            if repetitions == 0:
                interval_days = 1
            elif repetitions == 1:
                interval_days = 3
            elif repetitions == 2:
                interval_days = 7
            elif repetitions == 3:
                interval_days = 14
            elif repetitions == 4:
                interval_days = 30
            else:
                interval_days = min(interval_days * 1.5, 365)  # 最长一年
            
            repetitions += 1
            # ease_factor 可适当增加,简化版暂不调整
        else:  # 忘记或模糊
            repetitions = 0
            interval_days = 1 if quality == 1 else 0  # 模糊1天,忘记当天复习
            # ease_factor 降低,简化版暂不调整
        
        # 计算下次复习日期
        next_review = today + datetime.timedelta(days=interval_days)
        
        cursor.execute('''
            UPDATE words
            SET repetitions = ?, ease_factor = ?, interval_days = ?,
                last_review = ?, next_review = ?
            WHERE id = ?
        ''', (repetitions, ease_factor, interval_days, 
              today.isoformat(), next_review.isoformat(), word_id))
        
        conn.commit()
        conn.close()
    
    def get_today_count(self, exclude_ids=None):
        """获取今天待复习单词总数"""
        if exclude_ids is None:
            exclude_ids = set()
        
        today = datetime.date.today().isoformat()
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()
        
        placeholders = ','.join(['?'] * len(exclude_ids)) if exclude_ids else ''
        query = f'''
            SELECT COUNT(*) FROM words
            WHERE next_review <= ? AND id NOT IN ({placeholders})
        '''
        params = [today] + list(exclude_ids) if exclude_ids else [today]
        cursor.execute(query, params)
        count = cursor.fetchone()[0]
        conn.close()
        return count


class WordMemoryApp:
    """主应用程序类"""
    
    def __init__(self, root):
        self.root = root
        self.root.title("英语单词记忆助手 - 记忆曲线版")
        self.root.geometry("650x500")
        self.root.resizable(False, False)
        
        # 设置样式
        self.style = ttk.Style()
        self.style.configure('TLabel', font=('微软雅黑', 11))
        self.style.configure('Word.TLabel', font=('微软雅黑', 28, 'bold'))
        
        # 数据库实例
        self.db = WordMemoryDB()
        
        # 当前复习状态
        self.current_word_id = None
        self.current_word_data = None
        self.reviewed_ids = set()  # 本轮已复习的ID
        
        # 检查并导入单词本(仅在数据库为空时自动导入默认文件)
        self.md_path = "words.md"
        self.check_and_import_default()
        
        # 创建界面
        self.create_widgets()
        
        # 加载第一个单词
        self.load_next_word()
    
    def check_and_import_default(self):
        """检查默认单词本文件,如果数据库为空则自动导入"""
        # 检查数据库是否已有单词
        conn = sqlite3.connect(self.db.db_path)
        cursor = conn.cursor()
        cursor.execute("SELECT COUNT(*) FROM words")
        count = cursor.fetchone()[0]
        conn.close()
        
        if count == 0:
            if os.path.exists(self.md_path):
                try:
                    new = self.db.import_from_markdown(self.md_path)
                    if new > 0:
                        messagebox.showinfo("导入成功", f"已自动导入 {new} 个单词")
                except Exception as e:
                    messagebox.showerror("错误", f"自动导入失败:{str(e)}")
            else:
                # 创建示例文件
                example_content = """apple
苹果
例句:This is a red apple.

book
书
例句:I am reading a book.

computer
电脑
例句:My computer is fast.

python
蟒蛇 / Python编程语言
例句:Python is a popular programming language.
"""
                with open(self.md_path, 'w', encoding='utf-8') as f:
                    f.write(example_content)
                messagebox.showinfo("提示", f"未找到单词本,已创建示例文件:{self.md_path}\n请按格式编辑该文件后点击「重新导入单词本」导入。")
    
    def create_widgets(self):
        """创建界面组件"""
        # 顶部信息栏
        top_frame = ttk.Frame(self.root, padding=10)
        top_frame.pack(fill=X)
        
        self.count_label = ttk.Label(top_frame, text="今日剩余: 计算中...")
        self.count_label.pack(side=LEFT)
        
        import_btn = ttk.Button(top_frame, text="重新导入单词本", command=self.import_markdown)
        import_btn.pack(side=RIGHT)
        
        # 中间单词显示区
        mid_frame = ttk.Frame(self.root, padding=20)
        mid_frame.pack(expand=True, fill=BOTH)
        
        self.word_label = ttk.Label(mid_frame, text="", style='Word.TLabel')
        self.word_label.pack(pady=20)
        
        self.meaning_frame = ttk.Frame(mid_frame)
        self.meaning_frame.pack(fill=BOTH, expand=True, pady=10)
        
        self.meaning_label = ttk.Label(self.meaning_frame, text="", 
                                       font=('微软雅黑', 12), 
                                       wraplength=550, justify=LEFT)
        self.meaning_label.pack()
        
        self.example_label = ttk.Label(self.meaning_frame, text="",
                                       font=('微软雅黑', 11, 'italic'),
                                       foreground='gray',
                                       wraplength=550, justify=LEFT)
        self.example_label.pack(pady=5)
        
        # 底部按钮区
        bottom_frame = ttk.Frame(self.root, padding=10)
        bottom_frame.pack(fill=X)
        
        self.show_btn = ttk.Button(bottom_frame, text="显示释义", 
                                   command=self.show_meaning, width=15)
        self.show_btn.pack(side=LEFT, padx=5)
        
        # 评分按钮(初始隐藏)
        self.rating_frame = ttk.Frame(bottom_frame)
        self.rating_frame.pack(side=RIGHT)
        
        self.forget_btn = ttk.Button(self.rating_frame, text="忘记 (0)", 
                                     command=lambda: self.rate_word(0), 
                                     state=DISABLED, width=8)
        self.forget_btn.pack(side=LEFT, padx=2)
        
        self.hard_btn = ttk.Button(self.rating_frame, text="模糊 (1)", 
                                   command=lambda: self.rate_word(1), 
                                   state=DISABLED, width=8)
        self.hard_btn.pack(side=LEFT, padx=2)
        
        self.good_btn = ttk.Button(self.rating_frame, text="记得 (2)", 
                                   command=lambda: self.rate_word(2), 
                                   state=DISABLED, width=8)
        self.good_btn.pack(side=LEFT, padx=2)
    
    def update_count_display(self):
        """更新剩余单词计数"""
        count = self.db.get_today_count(self.reviewed_ids)
        self.count_label.config(text=f"今日剩余复习: {count} 个单词")
    
    def show_meaning(self):
        """显示释义和例句"""
        if self.current_word_data:
            _, _, meaning, example, _, _, _ = self.current_word_data
            self.meaning_label.config(text=f"【释义】 {meaning}")
            self.example_label.config(text=f"📖 {example}" if example else "")
            
            # 切换按钮状态
            self.show_btn.config(state=DISABLED)
            self.forget_btn.config(state=NORMAL)
            self.hard_btn.config(state=NORMAL)
            self.good_btn.config(state=NORMAL)
    
    def hide_meaning(self):
        """隐藏释义"""
        self.meaning_label.config(text="")
        self.example_label.config(text="")
        self.show_btn.config(state=NORMAL)
        self.forget_btn.config(state=DISABLED)
        self.hard_btn.config(state=DISABLED)
        self.good_btn.config(state=DISABLED)
    
    def load_next_word(self):
        """加载下一个待复习单词"""
        # 隐藏当前释义
        self.hide_meaning()
        
        # 获取下一个单词
        word_data = self.db.get_due_words(self.reviewed_ids)
        
        if word_data:
            self.current_word_id = word_data[0]
            self.current_word_data = word_data
            word = word_data[1]
            self.word_label.config(text=word)
            self.reviewed_ids.add(self.current_word_id)
        else:
            # 没有待复习单词了
            self.word_label.config(text="🎉 恭喜!今日复习完成!")
            self.current_word_id = None
            self.current_word_data = None
            self.show_btn.config(state=DISABLED)
        
        self.update_count_display()
    
    def rate_word(self, quality):
        """处理用户评分"""
        if self.current_word_id:
            # 更新数据库
            self.db.update_word_review(self.current_word_id, quality)
            
            # 加载下一个单词
            self.load_next_word()
    
    def import_markdown(self):
        """手动导入Markdown文件(仅添加新词)"""
        filename = filedialog.askopenfilename(
            title="选择单词本文件",
            filetypes=[("Markdown files", "*.md"), ("Text files", "*.txt"), ("All files", "*.*")]
        )
        if filename:
            try:
                new_count = self.db.import_from_markdown(filename)
                # 重置复习状态(新单词立即进入待复习队列)
                self.reviewed_ids.clear()
                self.load_next_word()
                messagebox.showinfo("成功", f"导入完成!新增 {new_count} 个单词")
            except Exception as e:
                messagebox.showerror("错误", f"导入失败:{str(e)}")


def main():
    root = Tk()
    app = WordMemoryApp(root)
    root.mainloop()


if __name__ == "__main__":
    main()

软件功能说明

核心特点

  1. 记忆曲线算法:采用简化版SM-2间隔重复算法,根据您的记忆情况自动安排复习时间
  2. Markdown单词本:支持从本地Markdown文件导入单词(格式示例见下方)
  3. 主动回忆模式:默认只显示英文单词,点击按钮后才显示释义,符合主动回忆学习法
  4. 进度追踪:实时显示今日剩余复习单词数量

使用方法

  1. 准备单词本
    创建words.md文件(与脚本同一目录),格式如下:
    apple
    苹果
    例句:This is a red apple.
    
    book
    书
    例句:I am reading a book.
    
    每个词条由单词、释义、例句三行组成,词条间用空行分隔。
  2. 运行软件
    python word_memory.py
    
    首次运行会自动创建示例单词本。
  3. 学习流程
    · 界面显示英文单词,释义和例句隐藏
    · 点击"显示释义"按钮查看中文意思和例句
    · 根据记忆情况选择:
    · 忘记 (0):完全没记住,单词会很快再次出现
    · 模糊 (1):有些印象但不确定,1天后复习
    · 记得 (2):正确回忆,根据记忆曲线安排下次复习
  4. 重新导入
    如需更新单词本,点击右上角"重新导入单词本"按钮选择新的MD文件。

记忆曲线算法说明

· 每次正确回答后,下次复习间隔会逐渐拉长:1天 → 3天 → 7天 → 14天 → 30天 → 最长1年
· 回答错误时,重新开始计数,并在当天或次日再次出现
· 系统每天自动计算需要复习的单词,优先显示最该复习的

打包为EXE

如需在未安装Python的电脑上运行,可使用PyInstaller打包:

pip install pyinstaller
pyinstaller --onefile --windowed --name "单词记忆助手" word_memory.py
posted @ 2026-03-12 09:37  hgrun  阅读(68)  评论(0)    收藏  举报