活动介绍

使用读取文件夹下所有excel文件中sheet_name=“功能点拆分表”的工作簿,需要识别单元格的颜色为黄色或红色的单元格,并提取单元格的信息,其他为空的单元格向上合并单元格,合并列表生成EXCEL后导出,给出面向对象代码

时间: 2024-03-26 17:36:13 浏览: 97
好的,可以使用Python中的pandas和openpyxl库来实现这个功能。下面是一个面向对象的示例代码: ```python import os import pandas as pd from openpyxl.styles import Color, PatternFill from openpyxl import load_workbook class ExcelExtractor: def __init__(self, folder_path): self.folder_path = folder_path self.sheet_name = "功能点拆分表" self.yellow_fill = PatternFill(start_color="FFFF00", end_color="FFFF00", fill_type="solid") self.red_fill = PatternFill(start_color="FF0000", end_color="FF0000", fill_type="solid") def extract_data(self): # 遍历文件夹下所有excel文件 for file in os.listdir(self.folder_path): if file.endswith(".xlsx"): file_path = os.path.join(self.folder_path, file) # 加载工作簿和工作表 workbook = load_workbook(file_path) sheet = workbook[self.sheet_name] # 提取单元格信息 data = [] for row in sheet.rows: row_data = [] for cell in row: if cell.fill == self.yellow_fill or cell.fill == self.red_fill: row_data.append(cell.value) elif cell.value is None and cell.coordinate != "A1": # 向上合并单元格 for merged_cell_range in sheet.merged_cells.ranges: if cell.coordinate in merged_cell_range: row_data.append(sheet.cell(merged_cell_range.min_row, merged_cell_range.min_col).value) break else: row_data.append(cell.value) data.append(row_data) # 生成新的工作簿 output_path = os.path.join(self.folder_path, f"{os.path.splitext(file)[0]}_output.xlsx") pd.DataFrame(data).to_excel(output_path, index=False, header=False) # 示例用法 extractor = ExcelExtractor("your_folder_path") extractor.extract_data() ``` 这个类的`extract_data`方法可以实现读取文件夹下所有excel文件中指定工作表的功能点拆分表,提取单元格信息,向上合并单元格,并生成新的工作簿。 其中,`self.yellow_fill`和`self.red_fill`是用来识别黄色和红色单元格的填充颜色,可以根据需要进行修改。在提取单元格信息时,如果遇到空单元格,就向上查找合并单元格,找到第一个非空单元格并提取其值。最后,使用pandas库将提取到的数据生成新的工作簿,并保存到原文件夹下。
阅读全文

相关推荐

import os import pandas as pd import tkinter as tk from tkinter import ttk, filedialog, scrolledtext, messagebox from tkinter.colorchooser import askcolor from difflib import SequenceMatcher import re import openpyxl import threading import numpy as np from openpyxl.utils import get_column_letter import xlrd import gc import hashlib import json import tempfile from concurrent.futures import ThreadPoolExecutor, as_completed import unicodedata class EnhancedSignalComparator: def __init__(self, root): self.root = root self.root.title("增强版信号功能对比工具") self.root.geometry("1200x800") self.root.configure(bg="#f0f0f0") # 初始化变量 self.folder_path = tk.StringVar() self.search_text = tk.StringVar() self.files = [] self.results = {} # 存储信号对比结果 self.highlight_color = "#FFD700" # 默认高亮色 self.search_running = False self.stop_requested = False self.cache_dir = os.path.join(tempfile.gettempdir(), "excel_cache") self.file_cache = {} # 文件缓存 self.column_cache = {} # 列名缓存 self.max_workers = 4 # 最大并发线程数 # 创建缓存目录 os.makedirs(self.cache_dir, exist_ok=True) # 创建界面 self.create_widgets() def create_widgets(self): # 顶部控制面板 control_frame = ttk.Frame(self.root, padding=10) control_frame.pack(fill=tk.X) # 文件夹选择 ttk.Label(control_frame, text="选择文件夹:").grid(row=0, column=0, sticky=tk.W) folder_entry = ttk.Entry(control_frame, textvariable=self.folder_path, width=50) folder_entry.grid(row=0, column=1, padx=5, sticky=tk.EW) ttk.Button(control_frame, text="浏览...", command=self.browse_folder).grid(row=0, column=2) # 搜索输入 ttk.Label(control_frame, text="搜索信号:").grid(row=1, column=0, sticky=tk.W, pady=(10,0)) search_entry = ttk.Entry(control_frame, textvariable=self.search_text, width=50) search_entry.grid(row=1, column=1, padx=5, pady=(10,0), sticky=tk.EW) search_entry.bind("<Return>", lambda event: self.start_search_thread()) ttk.Button(control_frame, text="搜索", command=self.start_search_thread).grid(row=1, column=2, pady=(10,0)) ttk.Button(control_frame, text="停止", command=self.stop_search).grid(row=1, column=3, pady=(10,0), padx=5) # 高级选项 ttk.Label(control_frame, text="并发线程:").grid(row=2, column=0, sticky=tk.W, pady=(10,0)) self.thread_var = tk.StringVar(value="4") ttk.Combobox(control_frame, textvariable=self.thread_var, values=["1", "2", "4", "8"], width=5).grid(row=2, column=1, sticky=tk.W, padx=5, pady=(10,0)) # 文件过滤 ttk.Label(control_frame, text="文件过滤:").grid(row=2, column=2, sticky=tk.W, pady=(10,0)) self.filter_var = tk.StringVar(value="*.xlsx;*.xlsm;*.xls") ttk.Entry(control_frame, textvariable=self.filter_var, width=20).grid(row=2, column=3, sticky=tk.W, padx=5, pady=(10,0)) # 高亮颜色选择 ttk.Label(control_frame, text="高亮颜色:").grid(row=3, column=0, sticky=tk.W, pady=(10,0)) self.color_btn = tk.Button(control_frame, bg=self.highlight_color, width=3, command=self.choose_color) self.color_btn.grid(row=3, column=1, sticky=tk.W, padx=5, pady=(10,0)) # 进度条 self.progress = ttk.Progressbar(control_frame, orient="horizontal", length=200, mode="determinate") self.progress.grid(row=3, column=2, columnspan=2, sticky=tk.EW, padx=5, pady=(10,0)) # 结果标签 self.result_label = ttk.Label(control_frame, text="") self.result_label.grid(row=3, column=4, sticky=tk.W, padx=5, pady=(10,0)) # 对比面板 notebook = ttk.Notebook(self.root) notebook.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 表格视图 self.table_frame = ttk.Frame(notebook) notebook.add(self.table_frame, text="表格视图") # 文本对比视图 self.text_frame = ttk.Frame(notebook) notebook.add(self.text_frame, text="行内容对比") # 状态栏 self.status_var = tk.StringVar() status_bar = ttk.Label(self.root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W) status_bar.pack(side=tk.BOTTOM, fill=tk.X) # 初始化表格和文本区域 self.init_table_view() self.init_text_view() def init_table_view(self): """初始化表格视图""" # 创建树状表格 columns = ("信号", "文件", "行内容摘要") self.tree = ttk.Treeview(self.table_frame, columns=columns, show="headings") # 设置列标题 for col in columns: self.tree.heading(col, text=col) self.tree.column(col, width=200, anchor=tk.W) # 添加滚动条 scrollbar = ttk.Scrollbar(self.table_frame, orient=tk.VERTICAL, command=self.tree.yview) self.tree.configure(yscrollcommand=scrollbar.set) self.tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # 绑定选择事件 self.tree.bind("<<TreeviewSelect>>", self.on_table_select) def init_text_view(self): """初始化文本对比视图""" self.text_panes = {} self.text_frame.columnconfigure(0, weight=1) self.text_frame.rowconfigure(0, weight=1) # 创建对比容器 self.compare_container = ttk.Frame(self.text_frame) self.compare_container.grid(row=0, column=0, sticky="nsew", padx=5, pady=5) # 添加差异高亮按钮 btn_frame = ttk.Frame(self.text_frame) btn_frame.grid(row=1, column=0, sticky="ew", padx=5, pady=5) ttk.Button(btn_frame, text="高亮显示差异", command=self.highlight_differences).pack(side=tk.LEFT) ttk.Button(btn_frame, text="导出差异报告", command=self.export_report).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="清除缓存", command=self.clear_cache).pack(side=tk.LEFT, padx=5) ttk.Button(btn_frame, text="手动指定列名", command=self.manual_column_select).pack(side=tk.LEFT, padx=5) def browse_folder(self): """选择文件夹""" folder = filedialog.askdirectory(title="选择包含Excel文件的文件夹") if folder: self.folder_path.set(folder) self.load_files() def load_files(self): """加载文件夹中的Excel文件(优化特殊字符处理)""" folder = self.folder_path.get() if not folder or not os.path.isdir(folder): return # 获取文件过滤模式 filter_patterns = self.filter_var.get().split(';') self.files = [] for file in os.listdir(folder): file_path = os.path.join(folder, file) # 跳过临时文件 if file.startswith('~$'): continue # 检查文件扩展名 file_lower = file.lower() matched = False for pattern in filter_patterns: # 移除通配符并转换为小写 ext = pattern.replace('*', '').lower() if file_lower.endswith(ext): matched = True break if matched: # 规范化文件名处理特殊字符 normalized_path = self.normalize_file_path(file_path) if normalized_path and os.path.isfile(normalized_path): self.files.append(normalized_path) self.status_var.set(f"找到 {len(self.files)} 个Excel文件") def normalize_file_path(self, path): """规范化文件路径,处理特殊字符""" try: # 尝试直接访问文件 if os.path.exists(path): return path # 尝试Unicode规范化 normalized = unicodedata.normalize('NFC', path) if os.path.exists(normalized): return normalized # 尝试不同编码方案 encodings = ['utf-8', 'shift_jis', 'euc-jp', 'cp932'] for encoding in encodings: try: decoded = path.encode('latin1').decode(encoding) if os.path.exists(decoded): return decoded except: continue # 最终尝试原始路径 return path except Exception as e: self.status_var.set(f"文件路径处理错误: {str(e)}") return path def get_file_hash(self, file_path): """计算文件哈希值用于缓存""" try: hash_md5 = hashlib.md5() with open(file_path, "rb") as f: for chunk in iter(lambda: f.read(4096), b""): hash_md5.update(chunk) return hash_md5.hexdigest() except Exception as e: self.status_var.set(f"计算文件哈希失败: {str(e)}") return str(os.path.getmtime(file_path)) def get_cache_filename(self, file_path): """获取缓存文件名""" file_hash = self.get_file_hash(file_path) return os.path.join(self.cache_dir, f"{os.path.basename(file_path)}_{file_hash}.cache") def load_header_cache(self, file_path): """加载列名缓存""" cache_file = self.get_cache_filename(file_path) if os.path.exists(cache_file): try: with open(cache_file, "r", encoding='utf-8') as f: return json.load(f) except: return None return None def save_header_cache(self, file_path, header_info): """保存列名缓存""" cache_file = self.get_cache_filename(file_path) try: with open(cache_file, "w", encoding='utf-8') as f: json.dump(header_info, f) return True except: return False def find_header_row(self, file_path): """查找列名行(增强版)""" # 检查缓存 cache = self.load_header_cache(file_path) if cache: return cache.get("header_row"), cache.get("signal_col") # 没有缓存则重新查找 if file_path.lower().endswith((".xlsx", ".xlsm")): return self.find_header_row_openpyxl(file_path) elif file_path.lower().endswith(".xls"): return self.find_header_row_xlrd(file_path) return None, None def find_header_row_openpyxl(self, file_path): """使用openpyxl查找列名行(增强版)""" try: wb = openpyxl.load_workbook(file_path, read_only=True, data_only=True) ws = wb.active # 尝试多种列名匹配模式 patterns = [ r'データ名', # 半角片假名 r'データ名', # 全角片假名 r'信号名', # 中文 r'Signal Name', # 英文 r'Data Name', r'信号名称', r'データ名称' ] # 扩大搜索范围:前100行和前100列 for row_idx in range(1, 101): # 1-100行 # 扩大列搜索范围到100列 for col_idx in range(1, 101): # 1-100列 try: cell = ws.cell(row=row_idx, column=col_idx) cell_value = cell.value if not cell_value: continue # 尝试所有匹配模式 cell_str = str(cell_value) for pattern in patterns: if re.search(pattern, cell_str, re.IGNORECASE): # 找到列名行后,尝试确定信号列 signal_col = None # 在同行中查找信号列 for col_idx2 in range(1, 101): # 1-100列 try: cell2 = ws.cell(row=row_idx, column=col_idx2) cell2_value = cell2.value if not cell2_value: continue cell2_str = str(cell2_value) if re.search(pattern, cell2_str, re.IGNORECASE): signal_col = col_idx2 break except: continue # 保存缓存 if signal_col is not None: header_info = {"header_row": row_idx, "signal_col": signal_col} self.save_header_cache(file_path, header_info) wb.close() return row_idx, signal_col except: continue wb.close() except Exception as e: self.status_var.set(f"查找列名行出错: {str(e)}") return None, None def find_header_row_xlrd(self, file_path): """使用xlrd查找列名行(增强版)""" try: wb = xlrd.open_workbook(file_path) ws = wb.sheet_by_index(0) # 尝试多种列名匹配模式 patterns = [ r'データ名', # 半角片假名 r'データ名', # 全角片假名 r'信号名', # 中文 r'Signal Name', # 英文 r'Data Name', r'信号名称', r'データ名称' ] # 扩大搜索范围:前100行和前100列 for row_idx in range(0, 100): # 0-99行 # 扩大列搜索范围到100列 for col_idx in range(0, 100): # 0-99列 try: cell_value = ws.cell_value(row_idx, col_idx) if not cell_value: continue # 尝试所有匹配模式 cell_str = str(cell_value) for pattern in patterns: if re.search(pattern, cell_str, re.IGNORECASE): # 找到列名行后,尝试确定信号列 signal_col = None # 在同行中查找信号列 for col_idx2 in range(0, 100): # 0-99列 try: cell2_value = ws.cell_value(row_idx, col_idx2) if not cell2_value: continue cell2_str = str(cell2_value) if re.search(pattern, cell2_str, re.IGNORECASE): signal_col = col_idx2 break except: continue # 保存缓存 if signal_col is not None: header_info = {"header_row": row_idx, "signal_col": signal_col} self.save_header_cache(file_path, header_info) return row_idx, signal_col except: continue except Exception as e: self.status_var.set(f"查找列名行出错: {str(e)}") return None, None def extract_row_content(self, ws, row_idx, header_row, max_cols=100): """高效提取行内容(最多到100列)""" content = [] # 扩展到100列 for col_idx in range(1, max_cols + 1): try: cell = ws.cell(row=row_idx, column=col_idx) if cell.value is not None and str(cell.value).strip() != '': # 使用列名缓存 col_key = f"{header_row}-{col_idx}" if col_key in self.column_cache: col_name = self.column_cache[col_key] else: col_name_cell = ws.cell(row=header_row, column=col_idx) col_name = col_name_cell.value if col_name_cell.value else f"列{get_column_letter(col_idx)}" self.column_cache[col_key] = col_name content.append(f"{col_name}: {str(cell.value).strip()}") except: continue return "\n".join(content) def start_search_thread(self): """启动搜索线程""" if self.search_running: return self.search_running = True self.stop_requested = False self.max_workers = int(self.thread_var.get()) threading.Thread(target=self.search_files, daemon=True).start() def stop_search(self): """停止搜索""" self.stop_requested = True self.status_var.set("正在停止搜索...") def search_files(self): """在文件中搜索内容(优化特殊文件处理)""" search_term = self.search_text.get().strip() if not search_term: self.status_var.set("请输入搜索内容") self.search_running = False return if not self.files: self.status_var.set("请先选择文件夹") self.search_running = False return # 重置结果和UI self.results = {} for item in self.tree.get_children(): self.tree.delete(item) total_files = len(self.files) processed_files = 0 found_signals = 0 # 使用线程池处理文件 with ThreadPoolExecutor(max_workers=self.max_workers) as executor: futures = {} for file_path in self.files: if self.stop_requested: break future = executor.submit(self.process_file, file_path, search_term) futures[future] = file_path # 处理完成的任务 for future in as_completed(futures): if self.stop_requested: break file_path = futures[future] try: found = future.result() found_signals += found processed_files += 1 # 更新进度 progress = int(processed_files / total_files * 100) self.progress["value"] = progress self.status_var.set(f"已处理 {processed_files}/{total_files} 个文件") self.root.update_idletasks() except Exception as e: self.status_var.set(f"处理文件 {os.path.basename(file_path)} 出错: {str(e)}") # 更新结果 if self.stop_requested: self.status_var.set(f"搜索已停止,已处理 {processed_files}/{total_files} 个文件") elif found_signals == 0: self.status_var.set(f"未找到包含 '{search_term}' 的信号") else: self.status_var.set(f"找到 {len(self.results)} 个匹配信号,共 {found_signals} 处匹配") self.update_text_view() self.progress["value"] = 0 self.search_running = False gc.collect() # 强制垃圾回收释放内存 def process_file(self, file_path, search_term): """处理单个文件(增强异常处理)""" found = 0 try: # 获取列名行和信号列 header_row, signal_col = self.find_header_row(file_path) # 如果自动查找失败,尝试手动模式 if header_row is None or signal_col is None: self.status_var.set(f"文件 {os.path.basename(file_path)} 未找到列名行,尝试手动查找...") header_row, signal_col = self.manual_find_header_row(file_path) if header_row is None or signal_col is None: self.status_var.set(f"文件 {os.path.basename(file_path)} 无法确定列名行,已跳过") return found # 根据文件类型处理 if file_path.lower().endswith((".xlsx", ".xlsm")): found = self.process_openpyxl_file(file_path, search_term, header_row, signal_col) elif file_path.lower().endswith(".xls"): found = self.process_xlrd_file(file_path, search_term, header_row, signal_col) except Exception as e: self.status_var.set(f"处理文件 {os.path.basename(file_path)} 出错: {str(e)}") return found def manual_find_header_row(self, file_path): """手动查找列名行(当自动查找失败时使用)""" try: # 尝试打开文件 if file_path.lower().endswith((".xlsx", ".xlsm")): wb = openpyxl.load_workbook(file_path, read_only=True, data_only=True) ws = wb.active # 扫描整个工作表(最多1000行) for row_idx in range(1, 1001): for col_idx in range(1, 101): try: cell = ws.cell(row=row_idx, column=col_idx) if cell.value and "データ" in str(cell.value): # 找到可能的列名行 return row_idx, col_idx except: continue wb.close() elif file_path.lower().endswith(".xls"): wb = xlrd.open_workbook(file_path) ws = wb.sheet_by_index(0) # 扫描整个工作表(最多1000行) for row_idx in range(0, 1000): for col_idx in range(0, 100): try: cell_value = ws.cell_value(row_idx, col_idx) if cell_value and "データ" in str(cell_value): # 找到可能的列名行 return row_idx, col_idx except: continue except: pass return None, None def process_openpyxl_file(self, file_path, search_term, header_row, signal_col): """高效处理xlsx/xlsm文件(增强异常处理)""" found = 0 try: # 使用缓存 file_key = f"{file_path}_{header_row}_{signal_col}" if file_key in self.file_cache: ws = self.file_cache[file_key] else: wb = openpyxl.load_workbook(file_path, data_only=True, read_only=True) ws = wb.active self.file_cache[file_key] = ws # 优化搜索范围 min_row = header_row + 1 max_row = min(ws.max_row, min_row + 5000) # 限制搜索行数 # 批量读取信号列数据 signal_values = [] for row in ws.iter_rows(min_row=min_row, max_row=max_row, min_col=signal_col, max_col=signal_col, values_only=True): signal_values.append(row[0]) # 搜索匹配的信号 for idx, value in enumerate(signal_values): if self.stop_requested: break if value is None: continue cell_str = str(value) if search_term.lower() in cell_str.lower(): signal = cell_str row_idx = min_row + idx # 提取整行内容 row_content = self.extract_row_content(ws, row_idx, header_row, max_cols=100) # 添加到结果集 if signal not in self.results: self.results[signal] = {} # 使用短文件名避免特殊字符问题 short_name = os.path.basename(file_path) self.results[signal][short_name] = row_content # 添加到表格(显示前50字符摘要) summary = row_content[:50] + "..." if len(row_content) > 50 else row_content self.tree.insert("", tk.END, values=(signal, short_name, summary)) found += 1 except Exception as e: self.status_var.set(f"处理文件 {os.path.basename(file_path)} 出错: {str(e)}") return found def process_xlrd_file(self, file_path, search_term, header_row, signal_col): """高效处理xls文件(增强异常处理)""" found = 0 try: # 使用缓存 file_key = f"{file_path}_{header_row}_{signal_col}" if file_key in self.file_cache: ws = self.file_cache[file_key] else: wb = xlrd.open_workbook(file_path) ws = wb.sheet_by_index(0) self.file_cache[file_key] = ws # 优化搜索范围 min_row = header_row + 1 max_row = min(ws.nrows, min_row + 5000) # 限制搜索行数 # 批量读取信号列数据 signal_values = ws.col_values(signal_col, min_row, max_row) # 搜索匹配的信号 for idx, value in enumerate(signal_values): if self.stop_requested: break if not value: continue cell_str = str(value) if search_term.lower() in cell_str.lower(): signal = cell_str row_idx = min_row + idx # 提取整行内容 row_content = self.extract_xlrd_row_content(ws, row_idx, header_row) # 添加到结果集 if signal not in self.results: self.results[signal] = {} # 使用短文件名避免特殊字符问题 short_name = os.path.basename(file_path) self.results[signal][short_name] = row_content # 添加到表格(显示前50字符摘要) summary = row_content[:50] + "..." if len(row_content) > 50 else row_content self.tree.insert("", tk.END, values=(signal, short_name, summary)) found += 1 except Exception as e: self.status_var.set(f"处理文件 {os.path.basename(file_path)} 出错: {str(e)}") return found def extract_xlrd_row_content(self, ws, row_idx, header_row): """为xls文件高效提取行内容""" content = [] try: row_values = ws.row_values(row_idx) except: return "" # 扩展到100列 for col_idx in range(min(len(row_values), 100)): try: cell_value = row_values[col_idx] if cell_value is not None and str(cell_value).strip() != '': # 使用列名缓存 col_key = f"{header_row}-{col_idx}" if col_key in self.column_cache: col_name = self.column_cache[col_key] else: try: col_name = ws.cell_value(header_row, col_idx) if not col_name: col_name = f"列{col_idx+1}" except: col_name = f"列{col_idx+1}" self.column_cache[col_key] = col_name content.append(f"{col_name}: {str(cell_value).strip()}") except: continue return "\n".join(content) def update_text_view(self): """更新文本对比视图""" # 清除现有文本区域 for widget in self.compare_container.winfo_children(): widget.destroy() if not self.results: return # 获取第一个信号作为默认显示 first_signal = next(iter(self.results.keys())) self.display_signal_comparison(first_signal) def on_table_select(self, event): """表格选择事件处理""" selected = self.tree.selection() if not selected: return item = self.tree.item(selected[0]) signal = item["values"][0] self.display_signal_comparison(signal) def display_signal_comparison(self, signal): """显示指定信号的对比""" # 清除现有文本区域 for widget in self.compare_container.winfo_children(): widget.destroy() if signal not in self.results: return signal_data = self.results[signal] files = list(signal_data.keys()) contents = list(signal_data.values()) # 创建列框架 for i, (file, content) in enumerate(zip(files, contents)): col_frame = ttk.Frame(self.compare_container) col_frame.grid(row=0, column=i, sticky="nsew", padx=5, pady=5) self.compare_container.columnconfigure(i, weight=1) # 文件名标签 file_label = ttk.Label(col_frame, text=file, font=("Arial", 10, "bold")) file_label.pack(fill=tk.X, pady=(0, 5)) # 文本区域 text_area = scrolledtext.ScrolledText(col_frame, wrap=tk.WORD, width=30, height=15) text_area.insert(tk.INSERT, content) text_area.configure(state="disabled") text_area.pack(fill=tk.BOTH, expand=True) # 保存引用 self.text_panes[file] = text_area def highlight_differences(self): """高亮显示文本差异""" if not self.text_panes: return # 获取所有行内容 all_contents = [] for text_area in self.text_panes.values(): text_area.configure(state="normal") text = text_area.get("1.0", tk.END).strip() text_area.configure(state="disabled") all_contents.append(text) # 如果所有内容相同,则不需要高亮 if len(set(all_contents)) == 1: self.status_var.set("所有文件行内容完全一致") return # 使用第一个文件作为基准 base_text = all_contents[0] # 对比并高亮差异 for i, (file, text_area) in enumerate(self.text_panes.items()): if i == 0: # 基准文件不需要处理 continue text_area.configure(state="normal") text_area.tag_configure("diff", background=self.highlight_color) # 清除之前的高亮 text_area.tag_remove("diff", "1.0", tk.END) # 获取当前文本 compare_text = text_area.get("1.0", tk.END).strip() # 使用序列匹配器查找差异 s = SequenceMatcher(None, base_text, compare_text) # 高亮差异部分 for tag in s.get_opcodes(): opcode = tag[0] start = tag[3] end = tag[4] if opcode != "equal": # 添加高亮标签 text_area.tag_add("diff", f"1.0+{start}c", f"1.0+{end}c") text_area.configure(state="disabled") self.status_var.set("差异已高亮显示") def choose_color(self): """选择高亮颜色""" color = askcolor(title="选择高亮颜色", initialcolor=self.highlight_color) if color[1]: self.highlight_color = color[1] self.color_btn.configure(bg=self.highlight_color) def export_report(self): """导出差异报告""" if not self.results: messagebox.showwarning("警告", "没有可导出的结果") return try: # 创建报告数据结构 report_data = [] for signal, files_data in self.results.items(): for file, content in files_data.items(): report_data.append({ "信号": signal, "文件": file, "行内容": content }) # 转换为DataFrame df = pd.DataFrame(report_data) # 保存到Excel save_path = filedialog.asksaveasfilename( defaultextension=".xlsx", filetypes=[("Excel文件", "*.xlsx")], title="保存差异报告" ) if save_path: df.to_excel(save_path, index=False) self.status_var.set(f"报告已保存到: {save_path}") except Exception as e: messagebox.showerror("错误", f"导出报告失败: {str(e)}") def clear_cache(self): """清除缓存""" try: for file in os.listdir(self.cache_dir): if file.endswith(".cache"): os.remove(os.path.join(self.cache_dir, file)) self.file_cache = {} self.column_cache = {} self.status_var.set("缓存已清除") except Exception as e: self.status_var.set(f"清除缓存失败: {str(e)}") def manual_column_select(self): """手动指定列名位置""" if not self.files: messagebox.showinfo("提示", "请先选择文件夹") return # 创建手动选择窗口 manual_window = tk.Toplevel(self.root) manual_window.title("手动指定列名位置") manual_window.geometry("400x300") # 文件选择 ttk.Label(manual_window, text="选择文件:").pack(pady=(10, 5)) file_var = tk.StringVar() file_combo = ttk.Combobox(manual_window, textvariable=file_var, values=[os.path.basename(f) for f in self.files]) file_combo.pack(fill=tk.X, padx=20, pady=5) file_combo.current(0) # 行号输入 ttk.Label(manual_window, text="列名行号:").pack(pady=(10, 5)) row_var = tk.StringVar(value="1") row_entry = ttk.Entry(manual_window, textvariable=row_var) row_entry.pack(fill=tk.X, padx=20, pady=5) # 列号输入 ttk.Label(manual_window, text="信号列号:").pack(pady=(10, 5)) col_var = tk.StringVar(value="1") col_entry = ttk.Entry(manual_window, textvariable=col_var) col_entry.pack(fill=tk.X, padx=20, pady=5) # 确认按钮 def confirm_selection(): try: file_idx = file_combo.current() file_path = self.files[file_idx] header_row = int(row_var.get()) signal_col = int(col_var.get()) # 保存到缓存 header_info = {"header_row": header_row, "signal_col": signal_col} self.save_header_cache(file_path, header_info) messagebox.showinfo("成功", f"已为 {os.path.basename(file_path)} 设置列名位置:行{header_row} 列{signal_col}") manual_window.destroy() except Exception as e: messagebox.showerror("错误", f"无效输入: {str(e)}") ttk.Button(manual_window, text="确认", command=confirm_selection).pack(pady=20) if __name__ == "__main__": root = tk.Tk() app = EnhancedSignalComparator(root) root.mainloop() 1、目前发现在处理大文件,且处理的行数不一致时,后一个文件会将前一个文件的缓存覆盖,导致两个文件的显示的结果都不对,比如A文件CGWHV:T,B文件MET(LO):T、MET(MID/HI):T,现在却变成了送信初期値:T 2、优化一下处理逻辑,现在对于大文件搜索还是太慢了,不需要处理额外的单元格,只需要找到信号值在的那一行,并且将那一行的内容与列名相对应起来。 3、在上述两个都完成的情况下,试着增加多信号处理的功能

import sys import pandas as pd import numpy as np import matplotlib.pyplot as plt from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QTableWidget, QTableWidgetItem, QFileDialog, QTabWidget, QLabel, QLineEdit, QComboBox, QMessageBox, QHeaderView, QDialog, QGroupBox, QTextEdit, QScrollArea, QSplitter, QFormLayout) from PyQt5.QtCore import Qt from sklearn.linear_model import LinearRegression from datetime import datetime import openpyxl from openpyxl.utils.dataframe import dataframe_to_rows import re import os class MaterialSystem(QMainWindow): def init(self): super().init() self.setWindowTitle(“辅料管理系统”) self.setGeometry(100, 100, 1200, 800) # 初始化变量 self.file_path = None self.df = None self.original_df = None self.production_data = {} self.historical_data = [] # 存储历史数据用于预测模型 # 创建主部件和布局 main_widget = QWidget() main_layout = QVBoxLayout() # 顶部按钮区域 top_layout = QHBoxLayout() self.load_button = QPushButton("加载Excel文件") self.load_button.clicked.connect(self.load_file) self.save_button = QPushButton("保存修改") self.save_button.clicked.connect(self.save_changes) self.save_button.setEnabled(False) self.predict_button = QPushButton("预测功能") self.predict_button.clicked.connect(self.show_prediction_dialog) self.predict_button.setEnabled(False) top_layout.addWidget(self.load_button) top_layout.addWidget(self.save_button) top_layout.addWidget(self.predict_button) top_layout.addStretch() # 标签显示文件路径 self.file_label = QLabel("未加载文件") top_layout.addWidget(self.file_label) main_layout.addLayout(top_layout) # 创建标签页 self.tabs = QTabWidget() self.table_tab = QWidget() self.production_tab = QWidget() self.history_tab = QWidget() # 新增历史数据标签页 # 表格标签页布局 table_layout = QVBoxLayout(self.table_tab) self.table_widget = QTableWidget() self.table_widget.setEditTriggers(QTableWidget.DoubleClicked) table_layout.addWidget(self.table_widget) # 产量数据标签页布局 production_layout = QVBoxLayout(self.production_tab) production_form_layout = QHBoxLayout() production_form_layout.addWidget(QLabel("阴极锌产量:")) self.zn_input = QLineEdit() production_form_layout.addWidget(self.zn_input) production_form_layout.addWidget(QLabel("电锌产量:")) self.dx_input = QLineEdit() production_form_layout.addWidget(self.dx_input) production_form_layout.addWidget(QLabel("渣处理量:")) self.zl_input = QLineEdit() production_form_layout.addWidget(self.zl_input) production_form_layout.addWidget(QLabel("硫酸产量:")) self.ls_input = QLineEdit() production_form_layout.addWidget(self.ls_input) production_form_layout.addWidget(QLabel("小窑焙砂处理量:")) self.by_input = QLineEdit() production_form_layout.addWidget(self.by_input) self.save_production_button = QPushButton("保存产量数据") self.save_production_button.clicked.connect(self.save_production_data) production_form_layout.addWidget(self.save_production_button) production_layout.addLayout(production_form_layout) production_layout.addStretch() # 历史数据标签页布局 history_layout = QVBoxLayout(self.history_tab) # 历史数据导入区域 history_group = QGroupBox("历史数据管理") history_group_layout = QVBoxLayout() self.import_button = QPushButton("导入历史数据") self.import_button.clicked.connect(self.import_historical_data) history_group_layout.addWidget(self.import_button) self.history_list = QTextEdit() self.history_list.setReadOnly(True) history_group_layout.addWidget(self.history_list) history_group.setLayout(history_group_layout) history_layout.addWidget(history_group) # 公式编辑区域 formula_group = QGroupBox("计算公式管理") formula_layout = QVBoxLayout() self.formula_edit = QTextEdit() self.formula_edit.setPlaceholderText("在此编辑计算公式...\n示例: 实际单耗 = 本月消耗 / 本月产量 * 1000") formula_layout.addWidget(self.formula_edit) self.save_formula_button = QPushButton("保存公式") self.save_formula_button.clicked.connect(self.save_formulas) formula_layout.addWidget(self.save_formula_button) formula_group.setLayout(formula_layout) history_layout.addWidget(formula_group) # 添加标签页 self.tabs.addTab(self.table_tab, "辅料数据") self.tabs.addTab(self.production_tab, "产量数据") self.tabs.addTab(self.history_tab, "历史数据与公式") # 新增标签页 main_layout.addWidget(self.tabs) main_widget.setLayout(main_layout) self.setCentralWidget(main_widget) # 初始化产量数据 self.init_production_data() # 初始化公式 self.formulas = { "实际单耗": "本月消耗 / 本月产量 * 1000", "定额单耗": "上月实际单耗 * 0.98", "定额消耗": "定额单耗 * 下月计划产量 / 1000" } self.update_formula_display() def update_formula_display(self): """更新公式显示""" text = "" for name, formula in self.formulas.items(): text += f"{name}: {formula}\n" self.formula_edit.setText(text) def save_formulas(self): """保存公式""" text = self.formula_edit.toPlainText() lines = text.split('\n') new_formulas = {} for line in lines: if ':' in line: parts = line.split(':', 1) name = parts[0].strip() formula = parts[1].strip() if name and formula: new_formulas[name] = formula if new_formulas: self.formulas = new_formulas QMessageBox.information(self, "成功", "公式已保存!") else: QMessageBox.warning(self, "警告", "未检测到有效的公式!") def import_historical_data(self): """导入历史数据""" file_paths, _ = QFileDialog.getOpenFileNames( self, "选择历史数据文件", "", "Excel Files (*.xlsx *.xls)" ) if not file_paths: return success_count = 0 for file_path in file_paths: try: # 读取历史数据 wb = openpyxl.load_workbook(file_path) sheet = wb.active # 提取数据 data = [] for row in sheet.iter_rows(values_only=True): data.append(row) # 提取产量数据 production_info = {} for row in data: if any(keyword in str(row[0]) for keyword in ["阴极锌", "电锌", "渣处理", "硫酸", "焙砂"]): parts = str(row[0]).split(';') for part in parts: if ':' in part: key_value = part.split(':', 1) key = key_value[0].strip() value = key_value[1].strip() # 提取数值部分 numbers = re.findall(r"[-+]?\d*\.\d+|\d+", value) if numbers: production_info[key] = float(numbers[0]) # 提取辅料消耗数据 material_consumption = {} for i, row in enumerate(data): if i >= 4: # 跳过标题行 if len(row) > 1 and row[1] and str(row[1]).strip() != "": material_name = str(row[1]).strip() # 确保有足够的列 if len(row) > 12 and row[12] is not None: try: consumption = float(row[12]) material_consumption[material_name] = consumption except (ValueError, TypeError): pass # 添加到历史数据 if production_info and material_consumption: history_entry = { "file": os.path.basename(file_path), "date": datetime.now().strftime("%Y-%m"), "production": production_info, "materials": material_consumption } self.historical_data.append(history_entry) success_count += 1 except Exception as e: QMessageBox.warning(self, "导入错误", f"文件 {os.path.basename(file_path)} 导入失败: {str(e)}") # 更新历史数据显示 self.update_history_display() QMessageBox.information(self, "导入完成", f"成功导入 {success_count}/{len(file_paths)} 个历史数据文件!") def update_history_display(self): """更新历史数据显示""" text = "已导入的历史数据:\n\n" for i, entry in enumerate(self.historical_data, 1): text += f"{i}. {entry['file']} ({entry['date']})\n" text += f" 产量: {entry['production']}\n" text += f" 辅料数量: {len(entry['materials'])}\n\n" self.history_list.setText(text) def init_production_data(self): """初始化产量数据""" self.production_data = { "阴极锌": 9293.226, "电锌": 8635.059, "渣处理量": 6514.5, "硫酸产量": 13143.5, "小窑焙砂处理量": 1095.9 } self.zn_input.setText(str(self.production_data["阴极锌"])) self.dx_input.setText(str(self.production_data["电锌"])) self.zl_input.setText(str(self.production_data["渣处理量"])) self.ls_input.setText(str(self.production_data["硫酸产量"])) self.by_input.setText(str(self.production_data["小窑焙砂处理量"])) def load_file(self): """加载Excel文件""" file_path, _ = QFileDialog.getOpenFileName( self, "打开Excel文件", "", "Excel Files (*.xlsx *.xls)" ) if file_path: self.file_path = file_path self.file_label.setText(f"已加载: {file_path}") try: # 使用openpyxl加载工作簿 wb = openpyxl.load_workbook(file_path) sheet = wb.active # 将工作表转换为DataFrame data = sheet.values headers = next(data) self.df = pd.DataFrame(data, columns=headers) self.original_df = self.df.copy() # 显示数据 self.display_table() # 启用按钮 self.save_button.setEnabled(True) self.predict_button.setEnabled(True) # 提取产量数据 self.extract_production_data() except Exception as e: QMessageBox.critical(self, "错误", f"加载文件失败: {str(e)}") # 重置状态 self.file_path = None self.df = None self.save_button.setEnabled(False) self.predict_button.setEnabled(False) self.file_label.setText("未加载文件") def extract_production_data(self): """从表格中提取产量数据 - 改进版本""" # 查找包含产量数据的行 for _, row in self.df.iterrows(): row_content = str(row[0]) # 检查是否包含产量关键词 if any(keyword in row_content for keyword in ["阴极锌", "电锌", "渣处理", "硫酸", "焙砂"]): # 使用更稳健的解析方法 parts = row_content.split(';') for part in parts: # 使用正则表达式提取数值 numbers = re.findall(r"[-+]?\d*\.\d+|\d+", part) if numbers: value = float(numbers[0]) if "阴极锌" in part: self.production_data["阴极锌"] = value elif "电锌" in part: self.production_data["电锌"] = value elif "渣处理" in part: self.production_data["渣处理量"] = value elif "硫酸" in part: self.production_data["硫酸产量"] = value elif "焙砂" in part: self.production_data["小窑焙砂处理量"] = value # 更新输入框 self.zn_input.setText(str(self.production_data["阴极锌"])) self.dx_input.setText(str(self.production_data["电锌"])) self.zl_input.setText(str(self.production_data["渣处理量"])) self.ls_input.setText(str(self.production_data["硫酸产量"])) self.by_input.setText(str(self.production_data["小窑焙砂处理量"])) break def save_production_data(self): """保存产量数据""" try: self.production_data["阴极锌"] = float(self.zn_input.text()) self.production_data["电锌"] = float(self.dx_input.text()) self.production_data["渣处理量"] = float(self.zl_input.text()) self.production_data["硫酸产量"] = float(self.ls_input.text()) self.production_data["小窑焙砂处理量"] = float(self.by_input.text()) QMessageBox.information(self, "成功", "产量数据已保存!") except ValueError: QMessageBox.warning(self, "错误", "请输入有效的数字!") def display_table(self): """在表格中显示数据 - 改进版本""" if self.df is not None: self.table_widget.clear() # 设置行列数 n_rows, n_cols = self.df.shape self.table_widget.setRowCount(n_rows) self.table_widget.setColumnCount(n_cols) # 设置表头 headers = self.df.columns.tolist() self.table_widget.setHorizontalHeaderLabels(headers) # 填充数据 for i in range(n_rows): for j in range(n_cols): # 安全获取单元格值 cell_value = self.df.iloc[i, j] if pd.isna(cell_value): cell_value = "" item = QTableWidgetItem(str(cell_value)) # 设置不可编辑的单元格 - 改进索引计算 # 仅当列数足够时才应用不可编辑 if j < len(headers): col_name = headers[j] # 对特定列设置为不可编辑 if any(keyword in col_name for keyword in ["单耗", "定额", "消耗量", "结果"]): item.setFlags(item.flags() & ~Qt.ItemIsEditable) item.setBackground(Qt.lightGray) self.table_widget.setItem(i, j, item) # 调整列宽 self.table_widget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) def save_changes(self): """保存修改到Excel文件 - 改进版本""" if self.df is None or self.file_path is None: return try: # 从表格中获取修改后的数据 for i in range(self.table_widget.rowCount()): for j in range(self.table_widget.columnCount()): if self.table_widget.item(i, j) is not None: new_value = self.table_widget.item(i, j).text() # 仅当列存在时才更新 if j < self.df.shape[1]: self.df.iloc[i, j] = new_value # 使用openpyxl保存,保留公式 wb = openpyxl.load_workbook(self.file_path) sheet = wb.active # 清除现有内容 for row in sheet.iter_rows(min_row=1, max_row=sheet.max_row, min_col=1, max_col=sheet.max_column): for cell in row: cell.value = None # 写入新数据 for r_idx, row in enumerate(dataframe_to_rows(self.df, index=False, header=True), 1): for c_idx, value in enumerate(row, 1): if c_idx <= sheet.max_column: # 确保列索引有效 sheet.cell(row=r_idx, column=c_idx, value=value) # 保存工作簿 wb.save(self.file_path) QMessageBox.information(self, "成功", "修改已保存到Excel文件!") except Exception as e: QMessageBox.critical(self, "错误", f"保存文件失败: {str(e)}") def show_prediction_dialog(self): """显示预测对话框""" if self.df is None: return dialog = PredictionDialog(self.df, self.production_data, self.historical_data, self.formulas, self) dialog.exec_() class PredictionDialog(QDialog): def init(self, df, production_data, historical_data, formulas, parent=None): super().init(parent) self.setWindowTitle(“辅料消耗预测”) self.setGeometry(200, 200, 1000, 800) self.df = df self.production_data = production_data self.historical_data = historical_data self.formulas = formulas self.material_models = {} # 存储每个辅料的预测模型 layout = QVBoxLayout() # 预测设置区域 settings_layout = QHBoxLayout() settings_layout.addWidget(QLabel("预测月份:")) self.month_combo = QComboBox() months = [f"{i}月" for i in range(1, 13)] current_month = datetime.now().month self.month_combo.addItems(months) self.month_combo.setCurrentIndex(current_month % 12) settings_layout.addWidget(self.month_combo) settings_layout.addWidget(QLabel("预测方法:")) self.method_combo = QComboBox() methods = ["基于产量比例", "线性回归", "移动平均"] if historical_data: methods.insert(0, "历史数据模型") self.method_combo.addItems(methods) settings_layout.addWidget(self.method_combo) self.predict_button = QPushButton("执行预测") self.predict_button.clicked.connect(self.run_prediction) settings_layout.addWidget(self.predict_button) layout.addLayout(settings_layout) # 产量输入区域 production_layout = QFormLayout() production_layout.addRow(QLabel("阴极锌产量:"), self.zn_pred_input := QLineEdit(str(production_data["阴极锌"]))) production_layout.addRow(QLabel("电锌产量:"), self.dx_pred_input := QLineEdit(str(production_data["电锌"]))) production_layout.addRow(QLabel("渣处理量:"), self.zl_pred_input := QLineEdit(str(production_data["渣处理量"]))) production_layout.addRow(QLabel("硫酸产量:"), self.ls_pred_input := QLineEdit(str(production_data["硫酸产量"]))) production_layout.addRow(QLabel("小窑焙砂处理量:"), self.by_pred_input := QLineEdit(str(production_data["小窑焙砂处理量"]))) layout.addLayout(production_layout) # 创建分割器用于结果和公式显示 splitter = QSplitter(Qt.Vertical) # 预测结果表格 result_widget = QWidget() result_layout = QVBoxLayout(result_widget) result_layout.addWidget(QLabel("预测结果:")) self.result_table = QTableWidget() self.result_table.setColumnCount(5) self.result_table.setHorizontalHeaderLabels(["车间", "辅料名称", "单位", "预测消耗量", "预测方法"]) self.result_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) result_layout.addWidget(self.result_table) # 公式显示区域 formula_widget = QWidget() formula_layout = QVBoxLayout(formula_widget) formula_layout.addWidget(QLabel("当前使用的公式:")) self.formula_display = QTextEdit() self.formula_display.setReadOnly(True) self.update_formula_display() formula_layout.addWidget(self.formula_display) splitter.addWidget(result_widget) splitter.addWidget(formula_widget) splitter.setSizes([500, 200]) layout.addWidget(splitter) # 图表按钮 self.chart_button = QPushButton("生成预测图表") self.chart_button.clicked.connect(self.generate_chart) layout.addWidget(self.chart_button) self.setLayout(layout) # 初始化时执行预测 self.run_prediction() def update_formula_display(self): """更新公式显示""" text = "" for name, formula in self.formulas.items(): text += f"{name}: {formula}\n" self.formula_display.setText(text) def run_prediction(self): """执行预测计算 - 增强版本""" # 清空表格 self.result_table.setRowCount(0) # 获取预测设置 prediction_method = self.method_combo.currentText() # 获取预测产量 try: zn_production = float(self.zn_pred_input.text()) dx_production = float(self.dx_pred_input.text()) zl_production = float(self.zl_pred_input.text()) ls_production = float(self.ls_pred_input.text()) by_production = float(self.by_pred_input.text()) except ValueError: QMessageBox.warning(self, "错误", "请输入有效的产量数据!") return # 筛选有效的辅料数据行 valid_rows = [] for idx, row in self.df.iterrows(): if idx >= 4: # 跳过标题行 workshop = row[0] material = row[1] unit = row[2] if len(row) > 2 else "" # 检查是否有消耗量数据 if len(row) > 12 and pd.notna(row[12]) and str(row[12]).strip() != "": try: consumption = float(row[12]) valid_rows.append((workshop, material, unit, consumption)) except (ValueError, TypeError): continue # 训练模型(如果需要) if prediction_method == "历史数据模型" and self.historical_data: self.train_models() # 执行预测 predictions = [] for workshop, material, unit, consumption in valid_rows: method_used = prediction_method predicted = None # 根据预测方法选择预测逻辑 if prediction_method == "历史数据模型" and material in self.material_models: # 使用历史数据训练的模型 try: # 根据车间确定使用哪个产量数据 if "浸出" in workshop: input_data = [[zn_production]] elif "熔铸" in workshop: input_data = [[dx_production]] elif "渣处理" in workshop: input_data = [[zl_production]] elif "硫酸" in workshop: input_data = [[ls_production]] elif "焙砂" in workshop: input_data = [[by_production]] else: input_data = [[(zn_production + dx_production + zl_production) / 3]] predicted = self.material_models[material].predict(input_data)[0] except: predicted = consumption * 1.05 # 模型失败时使用默认方法 elif prediction_method == "线性回归": # 使用线性回归模型(简化) predicted = consumption * 1.05 elif prediction_method == "移动平均": # 移动平均法 predicted = consumption * 1.03 else: # 基于产量比例 # 根据产量变化调整 if "浸出" in workshop: ratio = zn_production / self.production_data["阴极锌"] predicted = consumption * ratio elif "熔铸" in workshop: ratio = dx_production / self.production_data["电锌"] predicted = consumption * ratio elif "渣处理" in workshop: ratio = zl_production / self.production_data["渣处理量"] predicted = consumption * ratio elif "硫酸" in workshop: ratio = ls_production / self.production_data["硫酸产量"] predicted = consumption * ratio elif "焙砂" in workshop: ratio = by_production / self.production_data["小窑焙砂处理量"] predicted = consumption * ratio else: predicted = consumption * 1.02 # 默认增长2% method_used = "默认增长" # 应用自定义公式(如果有) if "定额消耗" in self.formulas: try: # 在实际应用中应使用更安全的公式解析 predicted = eval(self.formulas["定额消耗"], { "定额单耗": consumption / (self.production_data["阴极锌"] / 1000), "下月计划产量": zn_production }) except: pass if predicted is not None: predictions.append((workshop, material, unit, predicted, method_used)) # 显示预测结果 self.result_table.setRowCount(len(predictions)) for i, (workshop, material, unit, predicted, method) in enumerate(predictions): self.result_table.setItem(i, 0, QTableWidgetItem(str(workshop))) self.result_table.setItem(i, 1, QTableWidgetItem(material)) self.result_table.setItem(i, 2, QTableWidgetItem(unit)) self.result_table.setItem(i, 3, QTableWidgetItem(f"{predicted:.2f}")) self.result_table.setItem(i, 4, QTableWidgetItem(method)) def train_models(self): """使用历史数据训练预测模型""" if not self.historical_data: return self.material_models = {} # 收集每个辅料的历史数据 material_data = {} for entry in self.historical_data: for material, consumption in entry["materials"].items(): if material not in material_data: material_data[material] = [] # 根据辅料类型确定使用哪个产量数据 production_key = self.detect_production_key(material) if production_key in entry["production"]: production_value = entry["production"][production_key] material_data[material].append((production_value, consumption)) # 为每个辅料训练模型 for material, data in material_data.items(): if len(data) > 2: # 至少需要3个数据点 try: X = np.array([d[0] for d in data]).reshape(-1, 1) y = np.array([d[1] for d in data]) model = LinearRegression() model.fit(X, y) self.material_models[material] = model except: # 模型训练失败,跳过该辅料 pass def detect_production_key(self, material): """根据辅料名称确定关联的产量类型""" material_lower = material.lower() if "浸出" in material_lower or "阴极锌" in material_lower: return "阴极锌" elif "熔铸" in material_lower or "电锌" in material_lower: return "电锌" elif "渣处理" in material_lower or "渣量" in material_lower: return "渣处理量" elif "硫酸" in material_lower: return "硫酸产量" elif "焙砂" in material_lower: return "小窑焙砂处理量" else: return "阴极锌" # 默认 def generate_chart(self): """生成预测图表 - 增强版本""" if self.result_table.rowCount() == 0: QMessageBox.warning(self, "警告", "没有可用的预测数据!") return # 获取预测结果 data = [] for i in range(self.result_table.rowCount()): workshop = self.result_table.item(i, 0).text() material = self.result_table.item(i, 1).text() consumption = float(self.result_table.item(i, 3).text()) method = self.result_table.item(i, 4).text() data.append((f"{workshop}-{material}", consumption, method)) # 按消耗量排序 data = sorted(data, key=lambda x: x[1], reverse=True)[:15] # 创建图表 plt.figure(figsize=(12, 8)) names = [item[0] for item in data] values = [item[1] for item in data] methods = [item[2] for item in data] # 为不同方法分配颜色 color_map = { "历史数据模型": "green", "线性回归": "blue", "移动平均": "orange", "基于产量比例": "purple", "默认增长": "red" } colors = [color_map.get(method, "gray") for method in methods] plt.barh(names, values, color=colors) plt.xlabel('预测消耗量') plt.title('辅料消耗预测 (前15项)') # 添加图例 from matplotlib.patches import Patch legend_elements = [Patch(facecolor=color, label=method) for method, color in color_map.items()] plt.legend(handles=legend_elements, loc='lower right') plt.tight_layout() plt.show() if name == “main”: app = QApplication(sys.argv) window = MaterialSystem() window.show() sys.exit(app.exec_())帮我检查该代码中有什么问题

最新推荐

recommend-type

python利用openpyxl拆分多个工作表的工作簿的方法

总的来说,`openpyxl`库为处理Excel文件提供了丰富的功能,包括读取、写入以及对工作表的复杂操作。在拆分工作簿时,可以根据实际需求选择合适的方法,例如是否需要保留样式信息。理解并熟练运用这些方法,将有助于...
recommend-type

获取本机IP地址的程序源码分析

从给定文件信息中我们可以提取出的关键知识点是“取本机IP”的实现方法以及与之相关的编程技术和源代码。在当今的信息技术领域中,获取本机IP地址是一项基本技能,广泛应用于网络通信类的软件开发中,下面将详细介绍这一知识点。 首先,获取本机IP地址通常需要依赖于编程语言和操作系统的API。不同的操作系统提供了不同的方法来获取IP地址。在Windows操作系统中,可以通过调用Windows API中的GetAdaptersInfo()或GetAdaptersAddresses()函数来获取网络适配器信息,进而得到IP地址。在类Unix操作系统中,可以通过读取/proc/net或是使用系统命令ifconfig、ip等来获取网络接口信息。 在程序设计过程中,获取本机IP地址的源程序通常会用到网络编程的知识,比如套接字编程(Socket Programming)。网络编程允许程序之间进行通信,套接字则是在网络通信过程中用于发送和接收数据的接口。在许多高级语言中,如Python、Java、C#等,都提供了内置的网络库和类来简化网络编程的工作。 在网络通信类中,IP地址是区分不同网络节点的重要标识,它是由IP协议规定的,用于在网络中唯一标识一个网络接口。IP地址可以是IPv4,也可以是较新的IPv6。IPv4地址由32位二进制数表示,通常分为四部分,每部分由8位构成,并以点分隔,如192.168.1.1。IPv6地址则由128位二进制数表示,其表示方法与IPv4有所不同,以冒号分隔的8组16进制数表示,如2001:0db8:85a3:0000:0000:8a2e:0370:7334。 当编写源代码以获取本机IP地址时,通常涉及到以下几个步骤: 1. 选择合适的编程语言和相关库。 2. 根据目标操作系统的API或系统命令获取网络接口信息。 3. 分析网络接口信息,提取出IP地址。 4. 将提取的IP地址转换成适合程序内部使用的格式。 5. 在程序中提供相应功能,如显示IP地址或用于网络通信。 例如,在Python中,可以使用内置的socket库来获取本机IP地址。一个简单的示例代码如下: ```python import socket # 获取主机名 hostname = socket.gethostname() # 获取本机IP local_ip = socket.gethostbyname(hostname) print("本机IP地址是:", local_ip) ``` 在实际应用中,获取本机IP地址通常是为了实现网络通信功能,例如建立客户端与服务器的连接,或者是在开发涉及到IP地址的其他功能时使用。 关于文件名称“getIP”,这是一个自解释的文件名,明显表示该文件或程序的作用是获取本机的IP地址。从标签“控件 源码 网络通信类 资源”中我们可以看出,这个文件很可能是一个可以嵌入其他软件中的代码片段,用以实现网络通信功能的一部分,具有较高的灵活性和重用性。 综上所述,获取本机IP地址是一个涉及到操作系统API、网络编程以及特定编程语言知识的技能。它不仅需要对编程语言提供的库函数有深入的理解,还要对操作系统的网络配置和IP协议有足够的认识。通过阅读和分析相关的源代码,如“getIP”,可以加深对网络编程实现细节的理解,并能够在实际开发中快速地应用这一技术。
recommend-type

【权威指南】:Win11笔记本上RTX4080驱动的安装与调试秘籍

# 1. Win11笔记本与RTX4080显卡介绍 随着技术的不断发展,笔记本电脑已不再是仅仅满足办公需求的移动设备,而是逐渐成为可以胜任复杂图形处理和游戏任务的强大工具。微软推出的Windows 11操作系统,以其全新的用户界面和改进的系统性能,为用户提供了更为流畅的操作体验。而在硬件方面,NVIDIA的RTX 4080显卡以其卓越的图形处理能力和AI支持,为笔记本电脑带来了前所未有的性能提升。本章节将带您初步了解Windows 11笔记本与NVIDIA RTX 4080显卡的基础信息,为进一步深入探讨它们的优化和调试技巧奠定基础。 ## 1.1 Win11笔记本的优势 Windows
recommend-type

windows环境举例

<think>好的,用户现在想在Windows环境下创建C函数库,需要具体的方法和示例代码。我需要结合之前的回答和用户提供的引用内容来给出合适的指导。 首先回顾之前的回答,用户已经了解了在Linux下创建静态库和动态库的方法,现在需要切换到Windows环境。根据引用[2],Windows下的C标准库和动态链接库的处理与Linux不同,比如使用dlfcn.h在Linux,而Windows可能需要其他方式。另外,引用[1]提到了在Windows下配置gcc环境(MinGW-w64),这可能是一个关键点,因为用户可能需要使用MinGW来编译库。 用户提供的引用[3]提到了使用MSVC编译器,这
recommend-type

QQ自动发送/回复系统源代码开放

根据提供的文件信息,我们可以了解到以下几点关键的知识点: ### 标题:“qqhelp” 1. **项目类型**: 标题“qqhelp”暗示这是一个与QQ相关的帮助工具或项目。QQ是中国流行的即时通讯软件,因此这个标题表明项目可能提供了对QQ客户端功能的辅助或扩展。 2. **用途**: “help”表明此项目的主要目的是提供帮助或解决问题。由于它提到了QQ,并且涉及“autosend/reply”功能,我们可以推测该项目可能用于自动化发送消息回复,或提供某种形式的自动回复机制。 ### 描述:“I put it to my web, but nobody sendmessage to got the source, now I public it. it supply qq,ticq autosend/reply ,full sourcecode use it as you like” 1. **发布情况**: 描述提到该项目原先被放置在某人的网站上,并且没有收到请求源代码的消息。这可能意味着项目不够知名或者需求不高。现在作者决定公开发布,这可能是因为希望项目能够被更多人了解和使用,或是出于开源共享的精神。 2. **功能特性**: 提到的“autosend/reply”表明该项目能够实现自动发送和回复消息。这种功能对于需要进行批量或定时消息沟通的应用场景非常有用,例如客户服务、自动化的营销通知等。 3. **代码可用性**: 作者指出提供了“full sourcecode”,意味着源代码完全开放,用户可以自由使用,无论是查看、学习还是修改,用户都有很大的灵活性。这对于希望学习编程或者有特定需求的开发者来说是一个很大的优势。 ### 标签:“综合系统类” 1. **项目分类**: 标签“综合系统类”表明这个项目可能是一个多功能的集成系统,它可能不仅限于QQ相关的功能,还可能包含了其他类型的综合服务或特性。 2. **技术范畴**: 这个标签可能表明该项目的技术实现比较全面,可能涉及到了多个技术栈或者系统集成的知识点,例如消息处理、网络编程、自动化处理等。 ### 压缩包子文件的文件名称列表: 1. **Unit1.dfm**: 这是一个Delphi或Object Pascal语言的窗体定义文件,用于定义应用程序中的用户界面布局。DFM文件通常用于存储组件的属性和位置信息,使得开发者可以快速地进行用户界面的设计和调整。 2. **qqhelp.dpr**: DPR是Delphi项目文件的扩展名,包含了Delphi项目的核心设置,如程序入口、使用的单元(Units)等。这个文件是编译和构建Delphi项目的起点,它能够帮助开发者了解项目的组织结构和编译指令。 3. **Unit1.pas**: PAS是Delphi或Object Pascal语言的源代码文件。这个文件可能包含了与QQ帮助工具相关的核心逻辑代码,例如处理自动发送和回复消息的算法等。 4. **readme.txt**: 这是一个常见的文本文件,包含项目的基本说明和使用指导,帮助用户了解如何获取、安装、运行和定制该项目。README文件通常是用户与项目首次交互时首先阅读的文件,因此它对于一个开源项目的用户友好度有着重要影响。 通过以上分析,我们可以看出“qqhelp”项目是一个针对QQ通讯工具的自动化消息发送与回复的辅助工具。项目包含完整的源代码,用户可以根据自己的需要进行查看、修改和使用。它可能包含Delphi语言编写的窗体界面和后端逻辑代码,具有一定的综合系统特性。项目作者出于某种原因将其开源,希望能够得到更广泛的使用和反馈。
recommend-type

【7步打造Win11深度学习利器】:Tensorflow-GPU与RTX4080终极优化指南

# 1. 深度学习与Windows 11的融合 在人工智能时代,深度学习已渗透到生活的方方面面,而Windows 11作为最新一代的操作系统,为深度学习提供了一个高效的工作平台。本章节将探讨深度学习技术如何与Windows 11系统融合,以及这一
recommend-type

ue画笔画画蓝图版

<think>我们正在讨论的是在UnrealEngine中使用蓝图实现画笔绘画功能。根据用户需求,重点在于通过蓝图系统实现类似毛笔的绘画效果。结合之前的回答和引用内容,我们将详细展开实现方法。核心思路:通过捕捉输入轨迹,动态生成笔触网格,并应用材质模拟墨迹效果。###详细实现步骤####1.创建绘画蓝图创建一个名为`BP_PaintBrush`的Actor蓝图:-**根组件**:SceneComponent-**关键组件**:-`SplineComponent`:用于存储绘画路径点-`InstancedStaticMeshComponent`:高效渲染重复笔触段(替代单个SplineMesh组
recommend-type

VB.NET图表曲线组件实现多种图表绘制

在深入讨论所给文件信息中的知识点之前,我们首先需要明确这些信息所代表的内容。标题指出我们所讨论的是一款在VB.NET环境中使用的“三维图表曲线组件”。从描述中我们可以了解到该组件的功能特性,即它能够绘制包括柱状图、线条曲线图和饼图在内的多种类型图表,并且支持图例的展示。此外,组件的色彩使用比较鲜艳,它不仅适用于标准的Windows Forms应用程序,还能够在ASP.NET环境中使用。而“压缩包子文件的文件名称列表”提供的信息则指向了可能包含该组件示例代码或说明文档的文件名,例如“PSC_ReadMe_4556_10.txt”可能是一个说明文档,而“GraphingV3Testing”和“Graphing.V3”则可能是一些测试文件或组件的实际使用案例。 下面详细说明标题和描述中提到的知识点: 1. VB.NET环境中的图表组件开发: 在VB.NET中开发图表组件需要开发者掌握.NET框架的相关知识,包括但不限于Windows Forms应用程序的开发。VB.NET作为.NET框架的一种语言实现,它继承了.NET框架的面向对象特性和丰富的类库支持。图表组件作为.NET类库的一部分,开发者可以通过继承相关类、使用系统提供的绘图接口来设计和实现图形用户界面(GUI)中用于显示图表的部分。 2. 图表的类型和用途: - 柱状图:主要用于比较各类别数据的数量大小,通过不同长度的柱子来直观显示数据间的差异。 - 线条曲线图:适用于展示数据随时间或顺序变化的趋势,比如股票价格走势、温度变化等。 - 饼图:常用于展示各部分占整体的比例关系,可以帮助用户直观地了解数据的组成结构。 3. 图例的使用和意义: 图例在图表中用来说明不同颜色或样式所代表的数据类别或系列。它们帮助用户更好地理解图表中的信息,是可视化界面中重要的辅助元素。 4. ASP.NET中的图表应用: ASP.NET是微软推出的一种用于构建动态网页的框架,它基于.NET平台运行。在ASP.NET中使用图表组件意味着可以创建动态的图表,这些图表可以根据Web应用程序中实时的数据变化进行更新。比如,一个电子商务网站可能会利用图表组件来动态显示产品销售排行或用户访问统计信息。 5. 色彩运用: 在设计图表组件时,色彩的运用非常关键。色彩鲜艳不仅能够吸引用户注意,还能够帮助用户区分不同的数据系列。正确的色彩搭配还可以提高信息的可读性和美观性。 在技术实现层面,开发者可能需要了解如何在VB.NET中使用GDI+(Graphics Device Interface)进行图形绘制,掌握基本的绘图技术(如画线、填充、颜色混合等),并且熟悉.NET提供的控件(如Panel, Control等)来承载和显示这些图表。 由于提供的文件名列表中包含有"Testing"和".txt"等元素,我们可以推测该压缩包内可能还包含了与图表组件相关的示例程序和使用说明,这对于学习如何使用该组件将十分有用。例如,“GraphingV3Testing”可能是一个测试项目,用于在真实的应用场景中检验该图表组件的功能和性能;“PSC_ReadMe_4556_10.txt”可能是一个详细的用户手册或安装说明,帮助用户了解如何安装、配置和使用该组件。 总结而言,了解并掌握在VB.NET环境下开发和使用三维图表曲线组件的知识点,对从事.NET开发的程序员来说,不仅可以增强他们在数据可视化方面的技能,还可以提高他们构建复杂界面和动态交互式应用的能力。
recommend-type

【MultiExtractor_Pro实战演练】:8个复杂场景的高效数据提取解决方案

# 摘要 本文介绍了MultiExtractor_Pro工具的概述、基本使用技巧、实战演练、高级功能应用以及案例研究与展望。首先,详细说明了MultiExtractor_Pro的安装过程和用户界面布局,阐述了核心功能组件及其操作方法。接着,讲述了配置提取模板、设置提取任务以及实时数据提取与预览技巧。在实
recommend-type

k-means聚类分析算法推导

<think>首先,用户要求理解k-means聚类算法的数学推导过程。用户的问题是:“我想理解k-means聚类分析算法的数学推导过程 请问k-means clustering algorithm mathematical derivation steps”。所以,我需要提供k-means算法的数学推导步骤。 从系统级指令中,我有一些规则: - 所有行内数学表达式必须使用$...$格式。 - 独立公式必须使用$$...$$格式并单独成段。 - 使用LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,例如[^1]。 - 回答结构清晰,帮助用