1- # flake8 : noqa
2- # !/usr/bin/env python3
1+ # ruff : noqa: E501, UP015
2+ #!/usr/bin/env python3
33import json
44import re
5+ from collections import Counter
56from pathlib import Path
7+
68import requests
79
8- CACHE_FILE = 'problems_cache.json'
10+ SCRIPT_DIR = Path (__file__ ).parent
11+ PROJECT_ROOT = SCRIPT_DIR .parent
12+ CACHE_FILE = PROJECT_ROOT / 'problems_cache.json'
913GRAPHQL_URL = 'https://leetcode.com/graphql'
14+ LEETCODE_BASE_URL = 'https://leetcode.com/problems'
1015
1116
1217def fetch_full_problems_cache ():
@@ -63,11 +68,12 @@ def fetch_full_problems_cache():
6368
6469def load_problems_cache ():
6570 """Загружает кэш или создаёт новый (полный)."""
66- if Path ( CACHE_FILE ) .exists ():
71+ if CACHE_FILE .exists ():
6772 with open (CACHE_FILE , 'r' ) as f :
6873 return json .load (f )
6974 return fetch_full_problems_cache ()
7075
76+
7177def extract_problem_number (file_name : str ) -> int | None :
7278 """Извлекает номер задачи из имени файла."""
7379 if not file_name .endswith ('.py' ):
@@ -78,42 +84,70 @@ def extract_problem_number(file_name: str) -> int | None:
7884 return None
7985
8086
87+ def make_bar (value : int , max_val : int , width : int = 10 ) -> str :
88+ """Генерирует текстовый прогресс-бар из эмодзи."""
89+ if max_val == 0 :
90+ filled = 0
91+ else :
92+ filled = min (width , max (0 , int ((value / max_val ) * width )))
93+ return '█' * filled + '░' * (width - filled )
94+
95+
8196def main ():
82- script_dir = Path (__file__ ).parent
83- project_root = script_dir .parent
84- solutions_dir = project_root / 'solutions'
85- readme_path = project_root / 'README.md'
97+ solutions_dir = PROJECT_ROOT / 'solutions'
98+ readme_path = PROJECT_ROOT / 'README.md'
8699 problems = load_problems_cache ()
100+
87101 solved_files = []
88- stats = {'Easy' : 0 , 'Medium' : 0 , 'Hard' : 0 }
102+ solved_stats = {'Easy' : 0 , 'Medium' : 0 , 'Hard' : 0 }
89103 if solutions_dir .exists ():
90104 for f in solutions_dir .iterdir ():
91105 if f .is_file ():
92106 num = extract_problem_number (f .name )
93107 if num and str (num ) in problems :
94108 title , difficulty = problems [str (num )]
95109 solved_files .append ((num , title , difficulty , f .name ))
96- stats [difficulty ] += 1
110+ solved_stats [difficulty ] += 1
111+
97112 solved_files .sort (key = lambda x : x [0 ])
98- total = sum (stats .values ())
113+ total_solved = sum (solved_stats .values ())
114+
115+ counter = Counter (info [1 ] for info in problems .values ())
116+ total_stats = {
117+ 'Easy' : counter ['Easy' ],
118+ 'Medium' : counter ['Medium' ],
119+ 'Hard' : counter ['Hard' ]
120+ }
121+
122+ easy_bar = make_bar (solved_stats ['Easy' ], total_stats ['Easy' ])
123+ medium_bar = make_bar (solved_stats ['Medium' ], total_stats ['Medium' ])
124+ hard_bar = make_bar (solved_stats ['Hard' ], total_stats ['Hard' ])
125+
126+ easy_pct = (solved_stats ['Easy' ] / total_stats ['Easy' ] * 100 ) if total_stats ['Easy' ] else 0
127+ medium_pct = (solved_stats ['Medium' ] / total_stats ['Medium' ] * 100 ) if total_stats ['Medium' ] else 0
128+ hard_pct = (solved_stats ['Hard' ] / total_stats ['Hard' ] * 100 ) if total_stats ['Hard' ] else 0
129+
130+ stats_md = (
131+ f'✅ **Total**: **{ total_solved } ** \n '
132+ f'🟢 **Easy**: { solved_stats ["Easy" ]} `{ easy_bar } ` _({ easy_pct :.1f} %)_ \n '
133+ f'🟡 **Medium**: { solved_stats ["Medium" ]} `{ medium_bar } ` _({ medium_pct :.1f} %)_ \n '
134+ f'🔴 **Hard**: { solved_stats ["Hard" ]} `{ hard_bar } ` _({ hard_pct :.1f} %)_'
135+ )
136+
99137 if solved_files :
100138 table_rows = []
101139 for num , title , difficulty , file_name in solved_files :
102- leetcode_url = f'https://leetcode.com/problems /{ title .lower ().replace (" " , "-" )} /'
140+ leetcode_url = f'{ LEETCODE_BASE_URL } /{ title .lower ().replace (" " , "-" )} /'
103141 table_rows .append (
104142 f'| { num } | [{ title } ]({ leetcode_url } ) | { difficulty } | [`{ file_name } `](solutions/{ file_name } ) |'
105143 )
106144 table_md = '\n ' .join (table_rows )
107145 else :
108146 table_md = '| — | — | — | — |'
109- stats_md = (
110- f'- ✅ Solved: { total } \n '
111- f'- 🟢 Easy: { stats ["Easy" ]} \n '
112- f'- 🟡 Medium: { stats ["Medium" ]} \n '
113- f'- 🔴 Hard: { stats ["Hard" ]} '
114- )
147+
115148 with open (readme_path , 'r' ) as f :
116149 content = f .read ()
150+
117151 content = re .sub (
118152 r'(<!-- START_STATS -->\n).*?(<!-- END_STATS -->)' ,
119153 f'<!-- START_STATS -->\n { stats_md } \n <!-- END_STATS -->' ,
@@ -126,9 +160,10 @@ def main():
126160 content ,
127161 flags = re .DOTALL
128162 )
163+
129164 with open (readme_path , 'w' ) as f :
130165 f .write (content )
131- print (f'Updated README with { total } solved problems.' )
166+ print (f'Updated README with { total_solved } solved problems.' )
132167
133168
134169if __name__ == '__main__' :
0 commit comments