const fs = require('fs');
const path = require('path');
const summaryFile = path.resolve('HighFrequencyQuestionsSummary.md');
const binaryTreeFile = path.resolve('数据结构篇/二叉树.md');
const outputFile = path.resolve('anki_binarytree.txt');
function escapeForAnki(text) {
if (!text) return '';
return text
.replace(/\r\n/g, ' ')
.replace(/\n/g, ' ')
.replace(/\r/g, ' ')
.replace(/\t/g, ' ');
}
/**
* 简单的 JavaScript 语法高亮
*/
function highlightJS(code) {
let html = code
.replace(/&/g, '&')
.replace(//g, '>');
const keywords = ['const', 'let', 'var', 'function', 'return', 'if', 'else', 'while', 'for', 'break', 'continue', 'new', 'this', 'null', 'true', 'false', 'typeof', 'instanceof', 'class', 'extends', 'constructor', 'static', 'get', 'set', 'async', 'await', 'try', 'catch', 'throw', 'finally', 'of', 'in'];
const builtins = ['Math', 'Array', 'Object', 'String', 'Number', 'Boolean', 'Map', 'Set', 'console', 'prototype', 'length', 'push', 'pop', 'shift', 'unshift', 'slice', 'splice', 'concat', 'join', 'reverse', 'sort', 'filter', 'map', 'reduce', 'forEach', 'find', 'findIndex', 'includes', 'indexOf', 'fill', 'from', 'floor', 'ceil', 'max', 'min', 'abs', 'log', 'pow', 'sqrt', 'random', 'hasOwnProperty', 'toString', 'valueOf', 'substring', 'substr', 'split', 'trim', 'replace', 'match', 'test', 'exec', 'has', 'get', 'set', 'delete', 'add', 'clear', 'keys', 'values', 'entries', 'size', 'isEmpty', 'TreeNode', 'ListNode'];
html = html.replace(/(\/\/[^\n]*)/g, '$1');
html = html.replace(/(\/\*[\s\S]*?\*\/)/g, '$1');
html = html.replace(/("[^&]*?"|'[^']*?'|`[^`]*?`)/g, '$1');
html = html.replace(/\b(\d+\.?\d*)\b/g, '$1');
for (const kw of keywords) {
const regex = new RegExp(`\\b(${kw})\\b`, 'g');
html = html.replace(regex, '$1');
}
for (const builtin of builtins) {
const regex = new RegExp(`\\b(${builtin})\\b`, 'g');
html = html.replace(regex, '$1');
}
html = html.replace(/\b([a-zA-Z_$][a-zA-Z0-9_$]*)\s*\(/g, '$1(');
html = html.replace(/=>/g, '=>');
return html;
}
function formatCodeForAnki(code) {
if (!code) return '';
const highlighted = highlightJS(code.trim());
const withBreaks = highlighted
.replace(/\r\n/g, '
')
.replace(/\n/g, '
')
.replace(/\r/g, '
')
.replace(/\t/g, ' ');
return `
\`\`\`javascript
${withBreaks}
\`\`\`
`;
}
/**
* 从摘要文件中解析二叉树章节的题目
*/
function parseSummaryBinaryTree(content) {
const questions = [];
const lines = content.split('\n');
let inBinaryTreeSection = false;
let currentCategory = '';
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// 检测二叉树章节开始
if (line.includes('## 二、 二叉树')) {
inBinaryTreeSection = true;
continue;
}
// 检测二叉树章节结束(下一个二级标题)
if (inBinaryTreeSection && line.match(/^## [三四五六七八九十]/)) {
break;
}
if (!inBinaryTreeSection) continue;
const categoryMatch = line.match(/^\*\*(.+)\*\*$/);
if (categoryMatch) {
currentCategory = categoryMatch[1];
continue;
}
// 解析题目行
const questionMatch = line.match(/^- \[(.+?)\]\((.+?)\)\s*\((\w+)\)(?:\s*-\s*\*(.+?)\*)?/);
if (questionMatch) {
const title = questionMatch[1];
const link = questionMatch[2];
const difficulty = questionMatch[3];
const tag = questionMatch[4] || '';
let description = '';
if (i + 1 < lines.length && lines[i + 1].trim().startsWith('>')) {
description = lines[i + 1].trim().replace(/^>\s*/, '');
}
questions.push({ title, link, difficulty, tag, category: currentCategory, description });
}
}
return questions;
}
/**
* 从详细文件中提取题目对应的代码答案
*/
function parseBinaryTreeSolutions(content) {
const solutions = {};
const lines = content.split('\n');
let currentQuestionNum = null;
let currentCode = '';
let inCodeBlock = false;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
// 检测题目标题 - 多种格式
// [102. 二叉树的层序遍历]
// [236. 二叉树的最近公共祖先]
// [LCR 143. 树的子结构] -> 对应剑指 Offer 26
const titleMatch = line.match(/\[(\d+)[..\s]/);
if (titleMatch) {
const num = titleMatch[1];
if (num) {
currentQuestionNum = num;
if (!solutions[currentQuestionNum]) {
solutions[currentQuestionNum] = [];
}
}
}
// 特殊处理剑指 Offer 题目
if (line.includes('LCR 143') || line.includes('树的子结构')) {
currentQuestionNum = '26'; // 剑指 Offer 26
if (!solutions[currentQuestionNum]) {
solutions[currentQuestionNum] = [];
}
}
if (line.trim().startsWith('```js') || line.trim().startsWith('```javascript')) {
inCodeBlock = true;
currentCode = '';
continue;
}
if (line.trim() === '```' && inCodeBlock) {
inCodeBlock = false;
if (currentQuestionNum && currentCode.trim()) {
solutions[currentQuestionNum].push(currentCode.trim());
}
currentCode = '';
continue;
}
if (inCodeBlock) {
currentCode += line + '\n';
}
}
return solutions;
}
function generateAnkiCards(questions, solutions) {
const cards = [];
for (const q of questions) {
// 从题目标题中提取题号
const numMatch = q.title.match(/^(\d+)/);
let questionNum = numMatch ? numMatch[1] : null;
// 特殊处理剑指 Offer 题目
if (q.title.includes('剑指 Offer 26') || q.title.includes('树的子结构')) {
questionNum = '26';
}
// 正面 - 题目卡片 (绿色主题,适合二叉树)
let front = ``;
front += `
🌳 ${escapeForAnki(q.title)}
`;
const diffColor = q.difficulty === 'Easy' ? '#4CAF50' : q.difficulty === 'Medium' ? '#FF9800' : '#f44336';
const diffBg = q.difficulty === 'Easy' ? '#E8F5E9' : q.difficulty === 'Medium' ? '#FFF3E0' : '#FFEBEE';
front += `
${q.difficulty}`;
if (q.tag) front += ` ${escapeForAnki(q.tag)}`;
if (q.category) front += ` ${escapeForAnki(q.category)}`;
front += `
`;
if (q.description) {
front += `
${escapeForAnki(q.description)}
`;
}
front += `
📎 LeetCode 链接
`;
front += `
`;
// 反面 - 答案
let back = ``;
back += `
💡 解题代码
`;
if (questionNum && solutions[questionNum] && solutions[questionNum].length > 0) {
const codes = solutions[questionNum];
codes.forEach((code, idx) => {
if (codes.length > 1) {
back += `
📝 解法 ${idx + 1}
`;
}
back += formatCodeForAnki(code);
if (idx < codes.length - 1) back += `
`;
});
} else {
back += `
暂无代码答案,请参考 LeetCode 官方题解
`;
}
back += `
`;
cards.push({ front, back, title: q.title });
}
return cards;
}
function writeAnkiFile(cards, outputPath) {
const lines = cards.map(card => {
const front = card.front.replace(/[\r\n]+/g, ' ').replace(/\t/g, ' ');
const back = card.back.replace(/[\r\n]+/g, ' ').replace(/\t/g, ' ');
return `${front}\t${back}`;
});
fs.writeFileSync(outputPath, lines.join('\n'), 'utf8');
}
// 主程序
try {
console.log('📖 读取摘要文件...');
const summaryContent = fs.readFileSync(summaryFile, 'utf8');
console.log('📖 读取二叉树详解文件...');
const binaryTreeContent = fs.readFileSync(binaryTreeFile, 'utf8');
console.log('🔍 解析二叉树题目...');
const questions = parseSummaryBinaryTree(summaryContent);
console.log(` 找到 ${questions.length} 道二叉树题目`);
console.log('🔍 解析代码答案...');
const solutions = parseBinaryTreeSolutions(binaryTreeContent);
console.log(` 找到 ${Object.keys(solutions).length} 个题目的代码答案`);
console.log('🎨 生成带语法高亮的 Anki 卡片...');
const cards = generateAnkiCards(questions, solutions);
console.log('💾 写入文件...');
writeAnkiFile(cards, outputFile);
console.log(`\n✅ 成功生成 ${cards.length} 张二叉树 Anki 卡片!`);
console.log(`📁 输出文件: ${outputFile}`);
console.log('\n📋 Anki 导入说明:');
console.log(' 1. 打开 Anki -> 文件 -> 导入');
console.log(' 2. 选择 anki_binarytree.txt');
console.log(' 3. 类型: 基础');
console.log(' 4. 字段分隔符: Tab');
console.log(' 5. ✅ 务必勾选 "允许在字段中使用 HTML"');
console.log(' 6. 字段1 -> 正面, 字段2 -> 反面');
console.log('\n📊 卡片详情:');
let matchedCount = 0;
for (const card of cards) {
const hasAnswer = !card.back.includes('暂无代码答案');
if (hasAnswer) matchedCount++;
console.log(` ${hasAnswer ? '✅' : '❌'} ${card.title}`);
}
console.log(`\n 匹配率: ${matchedCount}/${cards.length} (${Math.round(matchedCount / cards.length * 100)}%)`);
} catch (err) {
console.error('❌ 错误:', err.message);
}