diff --git a/README.md b/README.md index 6967b1aaa128dbcecfc9b16a0e45d432fd2a433c..25ab28d52db7d808500d244c1e0ee9190a6b3c5a 100644 --- a/README.md +++ b/README.md @@ -56,14 +56,7 @@ pip install -r requirement.txt ```json { // ... - "export": [ - { - "file": "solution.c", - "variants": null, - "depends": [] - }, - // ... - ] + "export": ["solution.json"] } ``` @@ -74,117 +67,258 @@ pip install -r requirement.txt ## `知识节点` 的导出习题选项配置编辑 -首先,在知识节点下增加一个习题代码,例如在 `data/1.算法初阶/1.蓝桥杯/7段码` 下增加一个`solution.c`代码: +首先,我们根据前文,在 `data/1.算法初阶/1.蓝桥杯/7段码` 目录增加一个`solution.json`文件: -```c -#include -int main(int argc, char** argv){ - printf("Hello,Wrold!"); - return 0; +```json +{ + "type": "code_options", + "author": "卢昕", + "source": "solution.md" } ``` -其次,增加一个同名的选项配置文件`solution.json`,目前有两种配置规则 +然后在 `data/1.算法初阶/1.蓝桥杯/7段码` 下增加一个`solution.md`文件: -**单行替换规则**: +````markdown +# 7段码 +#### 题目描述 +小蓝要用七段码数码管来表示一种特殊的文字。 +![七段码](https://img-blog.csdnimg.cn/2020110916441977.png#pic_left) +上图给出了七段码数码管的一个图示,数码管中一共有 7 段可以发光的二极管,分别标记为 a, b, c, d, e, f, g。 -* 配置由`one_line`字段指定的单行替换字典 -* 格式是:`"<源字符串>"`: [`"<替换字符串A>"`, `<替换字符串B>`,...], - * 其中每个 `"<源字符串>"` `/` `"<替换字符串A>"` 被生成为是一个替换选项 - * 指定的配置应该能至少生成 `3+` 个替换选项 +小蓝要选择一部分二极管(至少要有一个)发光来表达字符。在设计字符的表达时,要求所有发光的二极管是连成一片的。 -```json -{ - "one_line": { - "printf": ["print"], - "return 0;": ["return 0"], - "(\"Hello,Wrold!\")": [" \"Hello,Wrold!\""] - } -} -``` +* 例如:b 发光,其他二极管不发光可以用来表达一种字符。 -上面的替换规则会将代码替换成 3 个变种的代码: - -```c -// 变种代码1 -#include -int main(int argc, char** argv){ - print("Hello,Wrold!"); - return 0; -} -``` +* 例如:c 发光,其他二极管不发光可以用来表达一种字符。 -```c -// 变种代码2 -#include -int main(int argc, char** argv){ - print("Hello,Wrold!"); - return 0 -} -``` +这种方案与上一行的方案可以用来表示不同的字符,尽管看上去比较相似。 -```c -// 变种代码3 -#include -int main(int argc, char** argv){ - print "Hello,Wrold!"; - return 0 -} -``` +* 例如:a, b, c, d, e 发光,f, g 不发光可以用来表达一种字符。 -这些变种代码将会作为技能树该知识点该代码选择题的选项。 +* 例如:b, f 发光,其他二极管不发光则不能用来表达一种字符,因为发光的二极管没有连成一片。 -**多行替换规则**: +请问,小蓝可以用七段码数码管表达多少种不同的字符? -* 配置由`multiline`字段指定的多行替换数组 -* 数组的每个元素是一组替换规则,会整组被替换 +## aop +### before +```cpp +#include +using namespace std; +int use[10]; +int ans, e[10][10], father[10]; +void init() +{ -例如: + e[1][2] = e[1][6] = 1; + e[2][1] = e[2][7] = e[2][3] = 1; + e[3][2] = e[3][4] = e[3][7] = 1; + e[4][3] = e[4][5] = 1; + e[5][4] = e[5][6] = e[5][7] = 1; + e[6][1] = e[6][5] = e[6][7] = 1; +} -```json +int find(int a) { - "multiline": [ - { - "printf": "print" - }, - { - "int main(int argc, char** argv){" : "int main(char** argv){", - "return 0;" : "return 0", - }, - { - "#include ": "" - } - ] + if (father[a] == a) + return a; + father[a] = find(father[a]); + return father[a]; } ``` +### after +```cpp +int main() +{ + init(); + dfs(1); + cout << ans; + return 0; +} -同样,该配置将支持将源代码生成3个变种代码 +``` -```c -// 变种代码1 -#include -int main(int argc, char** argv){ - print("Hello,Wrold!"); - return 0; +## 答案 +```cpp +void dfs(int d) +{ + if (d > 7) + { + for (int i = 1; i <= 7; i++) + { + father[i] = i; + } + + for (int i = 1; i < 8; i++) + { + for (int j = 1; j < 8; j++) + { + if (e[i][j] == 1 && use[i] && use[j]) + { + int fx = find(i); + int fy = find(j); + if (fx != fy) + { + father[fx] = fy; + } + } + } + } + int k = 0; + for (int i = 1; i < 8; i++) + { + if (use[i] && father[i] == i) + { + k++; + } + } + if (k == 1) + { + ans++; + } + return; + } + + use[d] = 1; + dfs(d + 1); + use[d] = 0; + dfs(d + 1); } ``` +## 选项 -```c -// 变种代码2, 注意第2组替换规则,包含了两行替换 -#include -int main(char** argv){ - print("Hello,Wrold!"); - return 0 +### A +```cpp +void dfs(int d) +{ + if (d > 7) + { + for (int i = 1; i <= 7; i++) + { + father[i] = i; + } + + for (int i = 1; i < 8; i++) + { + for (int j = 1; j < 8; j++) + { + if (e[i][j] == 1 && use[i] && use[j]) + { + int fx = find(i); + int fy = find(j); + if (fx != fy) + { + father[fx] = fy; + } + } + } + } + int k = 0; + for (int i = 1; i < 8; i++) + { + if (father[i] == i) + { + k++; + } + } + if (k == 1) + { + ans++; + } + return; + } + + use[d] = 1; + dfs(d + 1); + use[d] = 0; + dfs(d + 1); } ``` -```c -// 变种代码3 -int main(int argc, char** argv){ - print("Hello,Wrold!"); - return 0; +### B +```cpp +void dfs(int d) +{ + if (d > 7) + { + for (int i = 1; i <= 7; i++) + { + father[i] = i; + } + + for (int i = 1; i < 8; i++) + { + for (int j = 1; j < 8; j++) + { + if (e[i][j] == 1) + { + int fx = find(i); + int fy = find(j); + if (fx != fy) + { + father[fx] = fy; + } + } + } + } + int k = 0; + for (int i = 1; i < 8; i++) + { + if (use[i] && father[i] == i) + { + k++; + } + } + if (k == 1) + { + ans++; + } + return; + } + + use[d] = 1; + dfs(d + 1); + use[d] = 0; + dfs(d + 1); +} +``` + +### C +```cpp +void dfs(int d) +{ + if (d > 7) + { + for (int i = 1; i <= 7; i++) + { + father[i] = i; + } + + int k = 0; + for (int i = 1; i < 8; i++) + { + if (use[i] && father[i] == i) + { + k++; + } + } + if (k == 1) + { + ans++; + } + return; + } + + use[d] = 1; + dfs(d + 1); + use[d] = 0; + dfs(d + 1); } ``` +```` + +后续的处理程序会根据“答案”、“选项”等标题查找内容,选项章节内部的三级标题不会进入题目,可以用来标注选项信息,例如 +“语法错误”,“内存没有初始化”等等。 ## 技能树合成 diff --git "a/data/1.\347\256\227\346\263\225\345\210\235\351\230\266/1.\350\223\235\346\241\245\346\235\257/config.json" "b/data/1.\347\256\227\346\263\225\345\210\235\351\230\266/1.\350\223\235\346\241\245\346\235\257/config.json" index 3407cf95a3919655576413ce3c70fe407e0719d7..39528eaf2e7900c6e2759775c1b0635b66b80783 100644 --- "a/data/1.\347\256\227\346\263\225\345\210\235\351\230\266/1.\350\223\235\346\241\245\346\235\257/config.json" +++ "b/data/1.\347\256\227\346\263\225\345\210\235\351\230\266/1.\350\223\235\346\241\245\346\235\257/config.json" @@ -1,4 +1,5 @@ { - "node_id": "569d5e11c4fc5de7844053d9a733c5e8", - "keywords": [] + "node_id": "opencv-5437ea08671b4d9c888ad064723cce4d", + "keywords": [], + "title": "蓝桥杯" } \ No newline at end of file diff --git "a/data/1.\347\256\227\346\263\225\345\210\235\351\230\266/config.json" "b/data/1.\347\256\227\346\263\225\345\210\235\351\230\266/config.json" index 3407cf95a3919655576413ce3c70fe407e0719d7..06db73d86fe850be52c78a55a8db065655a00d63 100644 --- "a/data/1.\347\256\227\346\263\225\345\210\235\351\230\266/config.json" +++ "b/data/1.\347\256\227\346\263\225\345\210\235\351\230\266/config.json" @@ -1,4 +1,5 @@ { - "node_id": "569d5e11c4fc5de7844053d9a733c5e8", - "keywords": [] + "node_id": "opencv-978a11e5a53a4042bf096c5d244cb5ea", + "keywords": [], + "title": "算法初阶" } \ No newline at end of file diff --git a/data/config.json b/data/config.json index f1231c31900a3afc98b30f50e1f86b2522d1906f..fce789239abd804212418a20799485252942b26f 100644 --- a/data/config.json +++ b/data/config.json @@ -1,5 +1,6 @@ { "tree_name": "algorithm", "keywords": [], - "node_id": "569d5e11c4fc5de7844053d9a733c5e8" + "node_id": "569d5e11c4fc5de7844053d9a733c5e8", + "title": "C" } \ No newline at end of file diff --git a/src/__pycache__/tree.cpython-38.pyc b/src/__pycache__/tree.cpython-38.pyc index 58a068be9479b965110581eeb78c80b1d70ad5c2..b034f05a92591857f96e5847b263a3032e6f682d 100644 Binary files a/src/__pycache__/tree.cpython-38.pyc and b/src/__pycache__/tree.cpython-38.pyc differ diff --git a/src/tree.py b/src/tree.py index 57aac8555044bd4ced152fd4dd6863e93e98ee75..3b72d84a30c6abc9dcfefaed2db1785e75b5e9c8 100644 --- a/src/tree.py +++ b/src/tree.py @@ -5,6 +5,8 @@ import uuid import sys import re +id_set = set() + def load_json(p): with open(p, 'r') as f: @@ -20,7 +22,7 @@ def dump_json(p, j, exist_ok=False, override=False): print(f"{p} already exist") sys.exit(0) - with open(p, 'w') as f: + with open(p, 'w+') as f: f.write(json.dumps(j, indent=2, ensure_ascii=False)) @@ -37,11 +39,26 @@ def parse_no_name(d): return no, dir_name +def check_export(base, cfg): + flag = False + exports = [] + for export in cfg.get('export', []): + ecfg_path = os.path.join(base, export) + if os.path.exists(ecfg_path): + exports.append(export) + else: + flag = True + if flag: + cfg["export"] = exports + return flag + + def gen_tree(data_path): root = {} def gen_node_id(): - return ''.join(str(uuid.uuid5(uuid.NAMESPACE_URL, 'skill_tree')).split('-')) + # return ''.join(str(uuid.uuid5(uuid.NAMESPACE_URL, 'skill_tree')).split('-')) + return "opencv-" + uuid.uuid4().hex def list_dir(p): v = os.listdir(p) @@ -51,10 +68,44 @@ def gen_tree(data_path): if os.path.isdir(no_dir): yield no_dir, no_name - def ensure_node_id(cfg_path, cfg): - if cfg.get('node_id') is None: - cfg['node_id'] = gen_node_id() - dump_json(cfg_path, cfg, exist_ok=True, override=True) + def ensure_id_helper(node): + flag = False + + if (node.get('node_id') is None) or node.get('node_id') in id_set: + node['node_id'] = gen_node_id() + flag = True + + id_set.add(node['node_id']) + + if 'children' in node: + for c in node["children"]: + flag = flag or ensure_id_helper(list(c.values())[0]) + + return flag + + def ensure_node_id(cfg): + return ensure_id_helper(cfg) + + def ensure_title_helper(node, cfg_path, title=""): + flag = False + + if node.get('title') is None: + if cfg_path: + node['title'] = re.sub( + "^[0-9]{1,3}\.", "", os.path.split(os.path.dirname(cfg_path))[-1]) + else: + node['title'] = title + flag = True + + if 'children' in node: + for c in node["children"]: + flag = flag or ensure_title_helper( + list(c.values())[0], None, list(c.keys())[0]) + + return flag + + def ensure_title(cfg, cfg_path): + return ensure_title_helper(cfg, cfg_path) def make_node(name, node_id, keywords, children=None): node = {} @@ -69,7 +120,12 @@ def gen_tree(data_path): # 根节点 cfg_path = os.path.join(data_path, 'config.json') cfg = load_json(cfg_path) - ensure_node_id(cfg_path, cfg) + if ensure_node_id(cfg): + dump_json(cfg_path, cfg, exist_ok=True, override=True) + + if ensure_title(cfg, cfg_path): + cfg["title"] = "C" + dump_json(cfg_path, cfg, exist_ok=True, override=True) tree_node = { "node_id": cfg['node_id'], "keywords": cfg['keywords'], @@ -81,41 +137,63 @@ def gen_tree(data_path): for level_no_dir, level_no_name in list_dir(data_path): print(level_no_dir) no, level_name = parse_no_name(level_no_name) - cfg_path = os.path.join(level_no_dir, 'config.json') - cfg = load_json(cfg_path) - ensure_node_id(cfg_path, cfg) + level_path = os.path.join(level_no_dir, 'config.json') + level_cfg = load_json(level_path) + if ensure_node_id(level_cfg) or check_export(level_no_dir, level_cfg): + dump_json(level_path, level_cfg, exist_ok=True, override=True) + if ensure_title(level_cfg, level_path): + dump_json(level_path, level_cfg, exist_ok=True, override=True) level_node, level_node_children = make_node( - level_name, cfg['node_id'], cfg['keywords']) + level_name, level_cfg['node_id'], level_cfg['keywords']) tree_node['children'].append(level_node) # 章节点 for chapter_no_dir, chapter_no_name in list_dir(level_no_dir): no, chapter_name = parse_no_name(chapter_no_name) - cfg_path = os.path.join(chapter_no_dir, 'config.json') - ensure_node_id(cfg_path, cfg) - cfg = load_json(cfg_path) + chapter_path = os.path.join(chapter_no_dir, 'config.json') + chapter_cfg = load_json(chapter_path) + if ensure_node_id(chapter_cfg) or check_export(chapter_no_dir, chapter_cfg): + dump_json(chapter_path, chapter_cfg, + exist_ok=True, override=True) + if ensure_title(chapter_cfg, chapter_path): + dump_json(chapter_path, chapter_cfg, + exist_ok=True, override=True) chapter_node, chapter_node_children = make_node( - chapter_name, cfg['node_id'], cfg['keywords']) + chapter_name, chapter_cfg['node_id'], chapter_cfg['keywords']) level_node_children.append(chapter_node) # 知识点 for section_no_dir, section_no_name in list_dir(chapter_no_dir): - section_name = section_no_name - cfg_path = os.path.join(section_no_dir, 'config.json') - ensure_node_id(cfg_path, cfg) - cfg = load_json(cfg_path) + no, section_name = parse_no_name(section_no_name) + sec_path = os.path.join(section_no_dir, 'config.json') + sec_cfg = load_json(sec_path) + flag = ensure_node_id(sec_cfg) or check_export( + section_no_dir, sec_cfg) section_node, section_node_children = make_node( - section_name, cfg['node_id'], cfg['keywords'], cfg['children']) + section_name, sec_cfg['node_id'], sec_cfg['keywords'], sec_cfg.get('children', [])) chapter_node_children.append(section_node) # 确保习题分配了习题ID - for export in cfg['export']: - if export.get('exercise_id') is None: - export['exercise_id'] = gen_node_id() - dump_json(cfg_path, cfg, exist_ok=True, override=True) + + for export in sec_cfg.get("export", []): + ecfg_path = os.path.join(section_no_dir, export) + ecfg = load_json(ecfg_path) + + if (ecfg.get('exercise_id') is None) or (ecfg.get('exercise_id') in id_set): + ecfg['exercise_id'] = uuid.uuid4().hex + dump_json(ecfg_path, ecfg, + exist_ok=True, override=True) + + id_set.add(ecfg['exercise_id']) + + if flag: + dump_json(sec_path, sec_cfg, exist_ok=True, override=True) + + if ensure_title(sec_cfg, sec_path): + dump_json(sec_path, sec_cfg, exist_ok=True, override=True) # 保存技能树骨架 tree_path = os.path.join(data_path, 'tree.json')