改进
This commit is contained in:
22
src/heurams/interface/main.py
Normal file
22
src/heurams/interface/main.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
from textual.app import App, ComposeResult
|
||||||
|
from textual.widgets import Button, Header, Label, Footer
|
||||||
|
|
||||||
|
class HeurAMSApp(App):
|
||||||
|
#CSS_PATH = "question02.tcss"
|
||||||
|
TITLE = "潜进"
|
||||||
|
SUB_TITLE = "启发式辅助记忆调度器"
|
||||||
|
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
yield Header(show_clock = True)
|
||||||
|
|
||||||
|
yield Footer(show_command_palette = True)
|
||||||
|
|
||||||
|
def on_mount(self) -> None:
|
||||||
|
|
||||||
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||||
|
self.exit(event.button.id)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = HeurAMSApp()
|
||||||
|
app.run()
|
||||||
25
src/heurams/interface/widgets/fileexpr.py
Normal file
25
src/heurams/interface/widgets/fileexpr.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
from textual.widget import Widget
|
||||||
|
|
||||||
|
class FileSelector(Widget):
|
||||||
|
def __init__(self, *children: Widget, name: str | None = None, id: str | None = None, classes: str | None = None, disabled: bool = False, markup: bool = True) -> None:
|
||||||
|
super().__init__(*children, name=name, id=id, classes=classes, disabled=disabled, markup=markup)
|
||||||
|
def a(self):
|
||||||
|
file_list_widget = self.query_one("#file-list", ListView)
|
||||||
|
nucleon_path = pathlib.Path("./nucleon")
|
||||||
|
nucleon_files = sorted(
|
||||||
|
[f for f in nucleon_path.iterdir() if f.suffix == ".toml"],
|
||||||
|
key=lambda f: Glimpse(pt.NucleonUnion(f)).next_date,
|
||||||
|
reverse=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if nucleon_files:
|
||||||
|
for file in nucleon_files:
|
||||||
|
text = self.item_desc_generator(pathlib.Path(file))
|
||||||
|
file_list_widget.append(ListItem(
|
||||||
|
Label(text[0] + '\n' + text[1]),
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
file_list_widget.append(
|
||||||
|
ListItem(Static("在 ./nucleon/ 中未找到任何内容源数据文件.\n请放置文件后重启应用.\n或者新建空的单元集."))
|
||||||
|
)
|
||||||
|
file_list_widget.disabled = True
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
from .sm2 import SM2Algorithm
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'SM2Algorithm',
|
||||||
|
]
|
||||||
|
|
||||||
|
registry = {
|
||||||
|
"SM-2": SM2Algorithm,
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import heurams.services.timer as timer
|
import heurams.services.timer as timer
|
||||||
from typing import TypedDict
|
from typing import TypedDict
|
||||||
|
|
||||||
algo_name = "SM-2"
|
class SM2Algorithm:
|
||||||
|
algo_name = "SM-2"
|
||||||
|
|
||||||
class AlgodataDict(TypedDict):
|
class AlgodataDict(TypedDict):
|
||||||
efactor: float
|
efactor: float
|
||||||
real_rept: int
|
real_rept: int
|
||||||
rept: int
|
rept: int
|
||||||
@@ -13,7 +14,7 @@ class AlgodataDict(TypedDict):
|
|||||||
is_activated: int
|
is_activated: int
|
||||||
last_modify: float
|
last_modify: float
|
||||||
|
|
||||||
defaults = {
|
defaults = {
|
||||||
'efactor': 2.5,
|
'efactor': 2.5,
|
||||||
'real_rept': 0,
|
'real_rept': 0,
|
||||||
'rept': 0,
|
'rept': 0,
|
||||||
@@ -22,9 +23,10 @@ defaults = {
|
|||||||
'next_date': 0,
|
'next_date': 0,
|
||||||
'is_activated': 0,
|
'is_activated': 0,
|
||||||
'last_modify': timer.get_timestamp()
|
'last_modify': timer.get_timestamp()
|
||||||
}
|
}
|
||||||
|
|
||||||
def revisor(algodata: dict, feedback: int = 5, is_new_activation: bool = False):
|
@classmethod
|
||||||
|
def revisor(cls, algodata: dict, feedback: int = 5, is_new_activation: bool = False):
|
||||||
"""SM-2 算法迭代决策机制实现
|
"""SM-2 算法迭代决策机制实现
|
||||||
根据 quality(0 ~ 5) 进行参数迭代最佳间隔
|
根据 quality(0 ~ 5) 进行参数迭代最佳间隔
|
||||||
quality 由主程序评估
|
quality 由主程序评估
|
||||||
@@ -35,32 +37,36 @@ def revisor(algodata: dict, feedback: int = 5, is_new_activation: bool = False):
|
|||||||
if feedback == -1:
|
if feedback == -1:
|
||||||
return
|
return
|
||||||
|
|
||||||
algodata[algo_name]['efactor'] = algodata[algo_name]['efactor'] + (
|
algodata[cls.algo_name]['efactor'] = algodata[cls.algo_name]['efactor'] + (
|
||||||
0.1 - (5 - feedback) * (0.08 + (5 - feedback) * 0.02)
|
0.1 - (5 - feedback) * (0.08 + (5 - feedback) * 0.02)
|
||||||
)
|
)
|
||||||
algodata[algo_name]['efactor'] = max(1.3, algodata[algo_name]['efactor'])
|
algodata[cls.algo_name]['efactor'] = max(1.3, algodata[cls.algo_name]['efactor'])
|
||||||
|
|
||||||
if feedback < 3:
|
if feedback < 3:
|
||||||
algodata[algo_name]['rept'] = 0
|
algodata[cls.algo_name]['rept'] = 0
|
||||||
algodata[algo_name]['interval'] = 0
|
algodata[cls.algo_name]['interval'] = 0
|
||||||
else:
|
else:
|
||||||
algodata[algo_name]['rept'] += 1
|
algodata[cls.algo_name]['rept'] += 1
|
||||||
|
|
||||||
algodata[algo_name]['real_rept'] += 1
|
algodata[cls.algo_name]['real_rept'] += 1
|
||||||
|
|
||||||
if is_new_activation:
|
if is_new_activation:
|
||||||
algodata[algo_name]['rept'] = 0
|
algodata[cls.algo_name]['rept'] = 0
|
||||||
algodata[algo_name]['efactor'] = 2.5
|
algodata[cls.algo_name]['efactor'] = 2.5
|
||||||
|
|
||||||
if algodata[algo_name]['rept'] == 0:
|
if algodata[cls.algo_name]['rept'] == 0:
|
||||||
algodata[algo_name]['interval'] = 1
|
algodata[cls.algo_name]['interval'] = 1
|
||||||
elif algodata[algo_name]['rept'] == 1:
|
elif algodata[cls.algo_name]['rept'] == 1:
|
||||||
algodata[algo_name]['interval'] = 6
|
algodata[cls.algo_name]['interval'] = 6
|
||||||
else:
|
else:
|
||||||
algodata[algo_name]['interval'] = round(
|
algodata[cls.algo_name]['interval'] = round(
|
||||||
algodata[algo_name]['interval'] * algodata[algo_name]['efactor']
|
algodata[cls.algo_name]['interval'] * algodata[cls.algo_name]['efactor']
|
||||||
)
|
)
|
||||||
|
|
||||||
algodata[algo_name]['last_date'] = timer.get_daystamp()
|
algodata[cls.algo_name]['last_date'] = timer.get_daystamp()
|
||||||
algodata[algo_name]['next_date'] = timer.get_daystamp() + algodata[algo_name]['interval']
|
algodata[cls.algo_name]['next_date'] = timer.get_daystamp() + algodata[cls.algo_name]['interval']
|
||||||
algodata[algo_name]['last_modify'] = timer.get_timestamp()
|
algodata[cls.algo_name]['last_modify'] = timer.get_timestamp()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_due(cls, algodata):
|
||||||
|
return (algodata[cls.algo_name]['next_date'] <= timer.get_daystamp())
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
"""
|
||||||
|
Puzzle 模块 - 谜题生成系统
|
||||||
|
|
||||||
|
提供多种类型的谜题生成器,支持从字符串、字典等数据源导入题目
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .base import BasePuzzle
|
||||||
|
from .cloze import ClozePuzzle
|
||||||
|
from .mcq import MCQPuzzle
|
||||||
|
from .factory import PuzzleFactory
|
||||||
|
from .loader import PuzzleLoader
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
'BasePuzzle',
|
||||||
|
'ClozePuzzle',
|
||||||
|
'MCQPuzzle',
|
||||||
|
'PuzzleFactory',
|
||||||
|
'PuzzleLoader',
|
||||||
|
]
|
||||||
77
src/heurams/kernel/puzzles/factory.py
Normal file
77
src/heurams/kernel/puzzles/factory.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
"""
|
||||||
|
谜题工厂类 - 统一创建各种类型的谜题
|
||||||
|
"""
|
||||||
|
|
||||||
|
from typing import Dict, List, Union, Any
|
||||||
|
from .base import BasePuzzle
|
||||||
|
from .cloze import ClozePuzzle
|
||||||
|
from .mcq import MCQPuzzle
|
||||||
|
|
||||||
|
class PuzzleFactory:
|
||||||
|
"""谜题工厂,根据配置创建不同类型的谜题"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_cloze(text: str, min_denominator: int = 7) -> ClozePuzzle:
|
||||||
|
"""
|
||||||
|
创建填空题谜题
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text: 原始字符串,使用 "/" 分割句子
|
||||||
|
min_denominator: 最小概率倒数,控制填空数量
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
ClozePuzzle: 填空题实例
|
||||||
|
"""
|
||||||
|
return ClozePuzzle(text, min_denominator)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_mcq(
|
||||||
|
mapping: Dict[str, str],
|
||||||
|
jammer: List[str],
|
||||||
|
max_riddles_num: int = 2,
|
||||||
|
prefix: str = ""
|
||||||
|
) -> MCQPuzzle:
|
||||||
|
"""
|
||||||
|
创建选择题谜题
|
||||||
|
|
||||||
|
Args:
|
||||||
|
mapping: 题目-答案映射字典
|
||||||
|
jammer: 干扰项列表
|
||||||
|
max_riddles_num: 最大题目数量
|
||||||
|
prefix: 题目前缀
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
MCQPuzzle: 选择题实例
|
||||||
|
"""
|
||||||
|
return MCQPuzzle(mapping, jammer, max_riddles_num, prefix)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_config(config: Dict[str, Any]) -> BasePuzzle:
|
||||||
|
"""
|
||||||
|
根据配置字典创建谜题
|
||||||
|
|
||||||
|
Args:
|
||||||
|
config: 配置字典,包含谜题类型和参数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
BasePuzzle: 谜题实例
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: 当配置无效时抛出
|
||||||
|
"""
|
||||||
|
puzzle_type = config.get('type')
|
||||||
|
|
||||||
|
if puzzle_type == 'cloze':
|
||||||
|
return PuzzleFactory.create_cloze(
|
||||||
|
text=config['text'],
|
||||||
|
min_denominator=config.get('min_denominator', 7)
|
||||||
|
)
|
||||||
|
elif puzzle_type == 'mcq':
|
||||||
|
return PuzzleFactory.create_mcq(
|
||||||
|
mapping=config['mapping'],
|
||||||
|
jammer=config.get('jammer', []),
|
||||||
|
max_riddles_num=config.get('max_riddles_num', 2),
|
||||||
|
prefix=config.get('prefix', '')
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"未知的谜题类型: {puzzle_type}")
|
||||||
154
src/heurams/kernel/puzzles/loader.py
Normal file
154
src/heurams/kernel/puzzles/loader.py
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
"""
|
||||||
|
谜题加载器 - 从多种数据源加载谜题配置
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import yaml
|
||||||
|
import re
|
||||||
|
from typing import Dict, List, Union, Any
|
||||||
|
from pathlib import Path
|
||||||
|
from .factory import PuzzleFactory
|
||||||
|
|
||||||
|
|
||||||
|
class PuzzleLoader:
|
||||||
|
"""谜题加载器,支持从多种格式加载谜题配置"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_json(file_path: Union[str, Path]) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
从JSON文件加载谜题配置
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path: JSON文件路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict: 谜题配置字典
|
||||||
|
"""
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
return json.load(f)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_yaml(file_path: Union[str, Path]) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
从YAML文件加载谜题配置
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path: YAML文件路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict: 谜题配置字典
|
||||||
|
"""
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
return yaml.safe_load(f)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_text(text: str, puzzle_type: str = 'cloze', **kwargs) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
从纯文本字符串创建谜题配置
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text: 原始文本
|
||||||
|
puzzle_type: 谜题类型 ('cloze' 或 'mcq')
|
||||||
|
**kwargs: 其他参数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict: 谜题配置字典
|
||||||
|
"""
|
||||||
|
config = {'type': puzzle_type}
|
||||||
|
|
||||||
|
if puzzle_type == 'cloze':
|
||||||
|
config['text'] = text
|
||||||
|
config.update(kwargs)
|
||||||
|
elif puzzle_type == 'mcq':
|
||||||
|
# 从文本解析选择题配置
|
||||||
|
config.update(PuzzleLoader._parse_mcq_from_text(text, **kwargs))
|
||||||
|
else:
|
||||||
|
raise ValueError(f"不支持的谜题类型: {puzzle_type}")
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _parse_mcq_from_text(text: str, **kwargs) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
从文本解析选择题配置
|
||||||
|
|
||||||
|
Args:
|
||||||
|
text: 原始文本
|
||||||
|
**kwargs: 其他参数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict: 选择题配置
|
||||||
|
"""
|
||||||
|
# 简单的解析逻辑:每行一个题目,格式为 "题目:答案"
|
||||||
|
mapping = {}
|
||||||
|
lines = text.strip().split('\n')
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
line = line.strip()
|
||||||
|
if ':' in line:
|
||||||
|
question, answer = line.split(':', 1)
|
||||||
|
mapping[question.strip()] = answer.strip()
|
||||||
|
|
||||||
|
return {
|
||||||
|
'mapping': mapping,
|
||||||
|
'jammer': kwargs.get('jammer', []),
|
||||||
|
'max_riddles_num': kwargs.get('max_riddles_num', 2),
|
||||||
|
'prefix': kwargs.get('prefix', '')
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_csv(file_path: Union[str, Path], **kwargs) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
从CSV文件加载选择题配置
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path: CSV文件路径
|
||||||
|
**kwargs: 其他参数
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict: 选择题配置
|
||||||
|
"""
|
||||||
|
import csv
|
||||||
|
mapping = {}
|
||||||
|
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
reader = csv.reader(f)
|
||||||
|
for row in reader:
|
||||||
|
if len(row) >= 2:
|
||||||
|
mapping[row[0].strip()] = row[1].strip()
|
||||||
|
|
||||||
|
return {
|
||||||
|
'type': 'mcq',
|
||||||
|
'mapping': mapping,
|
||||||
|
'jammer': kwargs.get('jammer', []),
|
||||||
|
'max_riddles_num': kwargs.get('max_riddles_num', 2),
|
||||||
|
'prefix': kwargs.get('prefix', '')
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def load_puzzles_from_directory(directory: Union[str, Path]) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
从目录批量加载谜题配置
|
||||||
|
|
||||||
|
Args:
|
||||||
|
directory: 目录路径
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[Dict]: 谜题配置列表
|
||||||
|
"""
|
||||||
|
puzzles = []
|
||||||
|
dir_path = Path(directory)
|
||||||
|
|
||||||
|
# 加载JSON文件
|
||||||
|
for json_file in dir_path.glob('*.json'):
|
||||||
|
puzzles.append(PuzzleLoader.from_json(json_file))
|
||||||
|
|
||||||
|
# 加载YAML文件
|
||||||
|
for yaml_file in dir_path.glob('*.yaml'):
|
||||||
|
puzzles.append(PuzzleLoader.from_yaml(yaml_file))
|
||||||
|
|
||||||
|
# 加载CSV文件
|
||||||
|
for csv_file in dir_path.glob('*.csv'):
|
||||||
|
puzzles.append(PuzzleLoader.from_csv(csv_file))
|
||||||
|
|
||||||
|
return puzzles
|
||||||
Reference in New Issue
Block a user