/
raa
/
TTB
Обзор
Документация
Войти
/
raa
/
TTB
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
main.py
303 строки
11 KB
Andrey Ruzakov
update main.py
29 окт 2025, 09:06
29 окт 2025, 09:06
c847723
Код
Авторство
О чём код?
import re import tkinter as tk from tkinter import ttk, filedialog, messagebox import itertools import json from sympy import symbols, Implies, Equivalent, true as sympy_true import webbrowser A, B, C, D, E, F = symbols('A B C D E F') symbol_map = {'A': A, 'B': B, 'C': C, 'D': D, 'E': E, 'F': F} class LogicParser: """ expr := equiv equiv := impl ( '<->' impl )* impl := or_expr ( ('->' | '<-') or_expr )* or_expr := xor ( '|' xor )* xor := and_expr ( '^' and_expr )* and_expr := not_expr ( '&' not_expr )* not_expr := '~' not_expr | atom atom := SYMBOL | '(' expr ')' """ def __init__(self, text): self.text = text self.pos = 0 self.current = None self._next_token() def error(self, msg='Syntax error'): raise SyntaxError(f"{msg} at pos {self.pos}") def _next_token(self): t = self.text n = len(t) while self.pos < n and t[self.pos].isspace(): self.pos += 1 if self.pos >= n: self.current = ('EOF', None) return if t.startswith('<->', self.pos): self.current = ('EQUIV', '<->') self.pos += 3 elif t.startswith('->', self.pos): self.current = ('IMP', '->') self.pos += 2 elif t.startswith('<-', self.pos): self.current = ('RIMP', '<-') self.pos += 2 else: ch = t[self.pos] if ch in '()~&|^': self.current = (ch, ch) self.pos += 1 elif re.match(r'[A-Za-z]', ch, re.ASCII): m = re.match(r'[A-Za-z]\w*', t[self.pos:], re.ASCII) name = m.group(0) if name not in symbol_map: self.error(f"Unknown symbol '{name}'") self.current = ('SYM', name) self.pos += len(name) else: self.error(f"Invalid character '{ch}'") def _eat(self, tok_type): if self.current[0] == tok_type: val = self.current[1] self._next_token() return val else: self.error(f"Expected {tok_type}, got {self.current[0]}") def parse(self): node = self._parse_equiv() if self.current[0] != 'EOF': self.error("Extra input") return node def _parse_equiv(self): left = self._parse_impl() while self.current[0] == 'EQUIV': self._eat('EQUIV') right = self._parse_impl() left = Equivalent(left, right) return left def _parse_impl(self): left = self._parse_or() while self.current[0] in ('IMP', 'RIMP'): typ = self.current[0] self._next_token() right = self._parse_or() if typ == 'IMP': left = Implies(left, right) else: left = Implies(right, left) return left def _parse_or(self): left = self._parse_xor() while self.current[0] == '|': self._eat('|') right = self._parse_xor() left = left | right return left def _parse_xor(self): left = self._parse_and() while self.current[0] == '^': self._eat('^') right = self._parse_and() left = left ^ right return left def _parse_and(self): left = self._parse_not() while self.current[0] == '&': self._eat('&') right = self._parse_not() left = left & right return left def _parse_not(self): if self.current[0] == '~': self._eat('~') return ~self._parse_not() else: return self._parse_atom() def _parse_atom(self): if self.current[0] == '(': self._eat('(') node = self._parse_equiv() self._eat(')') return node elif self.current[0] == 'SYM': name = self.current[1] self._eat('SYM') return symbol_map[name] else: self.error(f"Expected symbol or '(', got {self.current[0]}") class TruthTableApp: def __init__ (self, master): self.master = master master.title("The truth table builder") master.grid_columnconfigure(0, weight=1) master.grid_rowconfigure(2, weight=1) top = tk.Frame(master) top.grid(row=0, column=0, sticky='ew', padx=10, pady=(10, 0)) top.grid_columnconfigure(1, weight=1) tk.Label(top, text="Logical expression:").grid(row=0, column=0, sticky='w') self.entry = tk.Entry(top, width=40) self.entry.grid(row=0, column=1, sticky='ew', padx=5) tk.Button(top, text="Build", command=self.build).grid(row=0, column=2) tk.Button(top, text="Reset", command=self.reset).grid(row=0, column=3, padx=5) self.filter_frame = tk.Frame(master) self.filter_frame.grid(row=1, column=0, sticky='ew', padx=10, pady=(5, 0)) self.tree = ttk.Treeview(master, show='headings') self.tree.grid(row=2, column=0, sticky='nsew', padx=10, pady=5) bot = tk.Frame(master) bot.grid(row=3, column=0, sticky='ew', padx=10, pady=5) tk.Button(bot, text="Save", command=self.save).pack(side='left') tk.Button(bot, text="Load", command=self.load).pack(side='left', padx=5) tk.Button(bot, text="Theory", command=self.show_theory).pack(side='right') self.full_table_data = [] self.filter_vars = {} self.last_expr_info = None def reset(self): self.entry.delete(0, 'end') for widget in self.filter_frame.winfo_children(): widget.destroy() self.tree.delete(*self.tree.get_children()) self.tree["columns"] = [] self.full_table_data = [] self.filter_vars = {} self.last_expr_info = None def parse(self, s: str): try: return LogicParser(s).parse() except SyntaxError as e: messagebox.showerror("Error", str(e)) return None def build(self): expr_str = self.entry.get().strip() if not expr_str: self.reset() messagebox.showwarning("Warning", "Input expression") return expr = self.parse(expr_str) if expr is None: return vars_list = sorted(expr.free_symbols, key=lambda v: v.name) if len(vars_list) > 6: messagebox.showerror("Error", "Maximum 6 variables") return cols = [v.name for v in vars_list] + ['Result'] self._setup_ui_for_columns(cols) self.full_table_data = [] for combo in itertools.product([0, 1], repeat=len(vars_list)): sub = {vars_list[i]: combo[i] for i in range(len(vars_list))} val = int(bool(expr.xreplace(sub))) row = list(combo) + [val] self.full_table_data.append(row) self.last_expr_info = { 'expr': expr_str, 'columns': cols,} self.apply_filters() def _setup_ui_for_columns(self, cols): for widget in self.filter_frame.winfo_children(): widget.destroy() self.tree.config(columns=cols) self.filter_vars = {} for i, col_name in enumerate(cols): self.filter_frame.grid_columnconfigure(i, weight=1) self.tree.heading(col_name, text=col_name) self.tree.column(col_name, anchor=tk.CENTER, width=70) filter_var = tk.StringVar(value="all") self.filter_vars[col_name] = filter_var values = ["all", "1", "0"] combo = ttk.Combobox(self.filter_frame, textvariable=filter_var, values=values, state="readonly") combo.grid(row=0, column=i, sticky='ew', padx=2) combo.bind('<<ComboboxSelected>>', lambda event: self.apply_filters()) def apply_filters(self): if not self.full_table_data: return cols = self.last_expr_info['columns'] filtered_table = [] for row in self.full_table_data: include_row = True for i, col_name in enumerate(cols): filter_value = self.filter_vars[col_name].get() if filter_value != "all": if int(filter_value) != row[i]: include_row = False break if include_row: filtered_table.append(row) self.last_expr_info['rows'] = filtered_table self.tree.delete(*self.tree.get_children()) for row in filtered_table: self.tree.insert('', 'end', values=row) def save(self): if not self.last_expr_info or 'rows' not in self.last_expr_info: messagebox.showwarning("Warning", "First build the table. Ыусщтв filter the table") return path = filedialog.asksaveasfilename(defaultextension=".json", filetypes=[("JSON", "*.json")]) if not path: return with open(path, 'w', encoding='utf-8') as f: json.dump(self.last_expr_info, f, ensure_ascii=False, indent=2) messagebox.showinfo("Save", f"to file {path}") def load(self): path = filedialog.askopenfilename(filetypes=[("JSON", "*.json")]) if not path: return try: with open(path, 'r', encoding='utf-8') as f: data = json.load(f) if 'expr' not in data or 'columns' not in data or 'rows' not in data: raise KeyError("The required data is missing in the file (expr, columns, rows).") self.reset() self.last_expr_info = data self.entry.insert(0, data['expr']) self.build() except FileNotFoundError: messagebox.showerror("Error", "File not found") except json.JSONDecodeError: messagebox.showerror("Error", "Error JSON.") except KeyError as e: messagebox.showerror("Error", f"Error file format: {e}") except Exception as e: messagebox.showerror("Error", f"Error load:{e}") def show_theory(self): theory = { 'negation': '¬A (~A) – true, when A is false.', 'conjunction': 'A ∧ B (&) – true, when both are true.', 'disjunction': 'A ∨ B (|) – true, when at least one is true.', 'exclusive or': 'A ⊕ B (^) – true, when exactly one is true.', 'implication': 'A → B (->) – false only if A is true and B is falase.', 'reverse implication': 'A ← B (<-) – false only if B is true and A is false.', 'equivalence': 'A ↔ B (<->) – true, when the values are equal.', } win = tk.Toplevel(self.master) win.title("Theory") txt = tk.Text(win, wrap='word', width=50, height=15) txt.pack(expand=True, fill='both', padx=10, pady=10) for k, v in theory.items(): txt.insert('end', f"{k}:\n {v}\n\n") txt.config(state='disabled') link_button = tk.Button(win, text="More detailed...", command=lambda: webbrowser.open_new("https://examer.ru/ege_po_informatike/teoriya/tablicy_istinnosti_i_logicheskie_sxemy")) link_button.pack(pady=5) def main(): root = tk.Tk() app = TruthTableApp(root) root.geometry("700x500") root.mainloop() if __name__ == '__main__': main()