1033. To Fill or Not to Fill (25)-PAT

本文介绍了一道PAT-A 1033的算法题,旨在找到从杭州出发到任意城市的最经济的加油路线。题目考虑了油箱容量限制、单位油耗距离及多个加油站的不同油价,通过贪心算法实现最优解。
With highways available, driving a car from Hangzhou to any other city is easy. But since the tank capacity of a car is limited, we have to find gas stations on the way from time to time. Different gas station may give different price. You are asked to carefully design the cheapest route to go.


Input Specification:


Each input file contains one test case. For each case, the first line contains 4 positive numbers: Cmax (<= 100), the maximum capacity of the tank; D (<=30000), the distance between Hangzhou and the destination city; Davg (<=20), the average distance per unit gas that the car can run; and N (<= 500), the total number of gas stations. Then N lines follow, each contains a pair of non-negative numbers: Pi, the unit gas price, and Di (<=D), the distance between this station and Hangzhou, for i=1,...N. All the numbers in a line are separated by a space.


Output Specification:


For each test case, print the cheapest price in a line, accurate up to 2 decimal places. It is assumed that the tank is empty at the beginning. If it is impossible to reach the destination, print "The maximum travel distance = X" where X is the maximum possible distance the car can run, accurate up to 2 decimal places.


Sample Input 1:
50 1300 12 8
6.00 1250
7.00 600
7.00 150
7.10 0
7.20 200
7.50 400
7.30 1000
6.85 300
Sample Output 1:
749.17
Sample Input 2:
50 1300 12 2
7.10 0
7.00 600
Sample Output 2:

The maximum travel distance = 1200.00

推荐指数:※※

来源:http://pat.zju.edu.cn/contests/pat-a-practise/1033

贪心,关键是贪心的策略。

想象一下自己在高速公路开车要加油的场景。

1.搜索当前可以到达的最便宜加油站的价格,与当前加油站价格比较。

如果比当前加油站大,那在当前加油站加满。到当前可以到达的最便宜的加油站的时候,再看要不要补充油。

如果比当前加油站小,这加正好可以到那个加油站的油。

重复,直到到达终点。

#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<float.h>
using namespace std;
typedef struct Station{
	float price;
	int dis;
}Station;
int compare(const void *a, const void *b){
	return ((Station*)a)->dis-((Station*)b)->dis;
}
float cheapest_price(float dis,int avg,int cap,Station *s,int n,bool *is_reach){
	if(s[0].dis>0){
		*is_reach=false;
		return 0;
	}
	int i,j;
	float curr_dis=0;
	float total_cost=0;
	float max_can=avg*cap;
	i=0;
	while(i<n){
		float tmp_cheap=FLT_MAX;
		int chs=-1;
		for(j=n-1;j>i;j--){//in the can reach station , choose a cheap and far
			if(s[j].dis<=s[i].dis+max_can&&s[j].price<tmp_cheap){
					tmp_cheap=s[j].price;
					chs=j;
			}
		}
		if(-1==chs){
			if(s[i].dis+max_can>=dis){
				total_cost+=((dis-curr_dis)/avg)*s[i].price;
				*is_reach=true;
				return total_cost;//if can reach ,return cost
			}
			else{
				*is_reach=false;
				curr_dis=s[i].dis+max_can;
				return curr_dis;//if can't reach, return dis
			}	
		}
		else{
			if(tmp_cheap>s[i].price){
				if(s[i].dis+max_can>=dis){
					total_cost+=((dis-curr_dis)/avg)*s[i].price;
					*is_reach=true;
					return total_cost;//if can reach ,return cost
				} 
				else
				{
					total_cost+=(cap-(curr_dis-s[i].dis)/avg)*s[i].price;
					curr_dis=s[i].dis+max_can;
				}
			}
			else{
				chs=-1;
				for(j=i+1;j<n;j++){//in the can reach station , choose a cheap and far
					if(s[j].price<=s[i].price){
						chs=j;
						break;
					}
				}
				total_cost+=((s[chs].dis-curr_dis)/avg)*s[i].price;
				curr_dis=s[chs].dis;
			}
			i=chs;
		}
	}
}
int main()
{
	int cap,dis,avg,n;
	int i;
	scanf("%d%d%d%d",&cap,&dis,&avg,&n);
	Station *s=new Station[n];
	for(i=0;i<n;i++)
		scanf("%f%d",&s[i].price,&s[i].dis);
	qsort(s,n,sizeof(Station),compare);
	bool is_reach=false;
	float cost=cheapest_price(dis,avg,cap,s,n,&is_reach);
	if(true==is_reach)
		printf("%.2f\n",cost);
	else
		printf("The maximum travel distance = %.2f\n",cost);
	return 0;

}

给几组测试用例,感谢 @badmartin提供:

50 1000 10 6
7.50 0
7.00 900
7.30 200
7.20 300
7.40 100
7.10 400

50 1000 10 8
8.00 1000
7.50 0
7.50 950
7.40 100
7.20 300
7.00 900
7.10 400
7.30 200

719.00
50 1300 10 2
7.10 10
7.00 400



# -*- coding: utf-8 -*- import os import re import sys import time import threading import tkinter as tk from tkinter import ttk, filedialog, messagebox, scrolledtext from tkinter.font import Font import fnmatch import subprocess import shutil import docx from openpyxl import load_workbook import PyPDF2 import zipfile import chardet import xlrd import tempfile class EnhancedFileSearchApp: def __init__(self, master): self.master = master master.title("🎯 高级文件搜索工具") master.geometry("1200x800") master.minsize(1000, 700) # 设置现代主题和颜色方案 theme = "vista" if sys.platform == "win32" else "aqua" self.style = ttk.Style() self.style.theme_use(theme) # 自定义配色方案 self.colors = { "bg": "#f5f6fa", "header": "#3498db", "accent": "#2980b9", "warning": "#e74c3c", "success": "#2ecc71", "text": "#2c3e50", "highlight": "#f1c40f" } # 设置主窗口背景色 master.configure(bg=self.colors["bg"]) # 创建主框架 main_frame = ttk.Frame(master, padding=(15, 15)) main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # 创建搜索面板(顶部) search_frame = ttk.LabelFrame( main_frame, text="⚙️ 搜索选项", padding=(15, 10), style="Search.TLabelframe" ) search_frame.pack(side=tk.TOP, fill=tk.X, padx=0, pady=(0, 10)) # 修改为TOP布局 # 配置搜索面板样式 self.style.configure("Search.TLabelframe", background=self.colors["bg"], bordercolor=self.colors["accent"], lightcolor=self.colors["accent"]) self.style.configure("Search.TLabelframe.Label", font=("Arial", 10, "bold"), foreground=self.colors["header"]) # 搜索目录 dir_frame = ttk.Frame(search_frame) dir_frame.pack(fill=tk.X, pady=(0, 10)) ttk.Label(dir_frame, text="📁 搜索目录:", font=("Arial", 9, "bold")).pack(side=tk.LEFT, padx=(0, 5)) self.dir_entry = ttk.Entry(dir_frame, width=35) self.dir_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5)) self.dir_entry.insert(0, os.getcwd()) browse_btn = ttk.Button( dir_frame, text="浏览...", command=self.browse_directory, style="Accent.TButton" ) browse_btn.pack(side=tk.RIGHT) # 关键词 kw_frame = ttk.Frame(search_frame) kw_frame.pack(fill=tk.X, pady=5) ttk.Label(kw_frame, text="🔍 关键词:", font=("Arial", 9, "bold")).pack(side=tk.LEFT, padx=(0, 5)) self.keyword_entry = ttk.Entry(kw_frame, width=40) self.keyword_entry.pack(side=tk.LEFT, fill=tk.X, expand=True) # 文件过滤 filter_frame = ttk.Frame(search_frame) filter_frame.pack(fill=tk.X, pady=5) ttk.Label(filter_frame, text="📄 文件过滤:", font=("Arial", 9, "bold")).pack(side=tk.LEFT, padx=(0, 5)) self.filter_entry = ttk.Entry(filter_frame, width=40) self.filter_entry.pack(side=tk.LEFT, fill=tk.X, expand=True) self.filter_entry.insert(0, "*.c;*.h;*.prm;*.xlsx;*.xls;*.doc;*.docx;*.pdf") # 搜索选项 options_frame = ttk.Frame(search_frame) options_frame.pack(fill=tk.X, pady=10) self.case_var = tk.BooleanVar(value=False) case_chk = ttk.Checkbutton( options_frame, text="忽略大小写", variable=self.case_var, style="Custom.TCheckbutton" ) case_chk.pack(side=tk.LEFT, padx=(0, 15)) self.regex_var = tk.BooleanVar(value=False) regex_chk = ttk.Checkbutton( options_frame, text="正则表达式", variable=self.regex_var, style="Custom.TCheckbutton" ) regex_chk.pack(side=tk.LEFT, padx=(0, 15)) self.binary_var = tk.BooleanVar(value=False) binary_chk = ttk.Checkbutton( options_frame, text="包含二进制", variable=self.binary_var, style="Custom.TCheckbutton" ) binary_chk.pack(side=tk.LEFT, padx=(0, 15)) self.highlight_var = tk.BooleanVar(value=True) highlight_chk = ttk.Checkbutton( options_frame, text="关键字高亮", variable=self.highlight_var, style="Custom.TCheckbutton" ) highlight_chk.pack(side=tk.LEFT) # 配置选项样式 self.style.configure("Custom.TCheckbutton", font=("Arial", 9), foreground=self.colors["text"]) # 按钮组 btn_frame = ttk.Frame(search_frame) btn_frame.pack(fill=tk.X, pady=(15, 5)) self.search_btn = ttk.Button( btn_frame, text="🔎 开始搜索", command=self.start_search, style="Accent.TButton" ) self.search_btn.pack(side=tk.LEFT, padx=(0, 10)) self.stop_btn = ttk.Button( btn_frame, text="⏹️ 停止", command=self.stop_search, style="Warning.TButton", state=tk.DISABLED ) self.stop_btn.pack(side=tk.LEFT, padx=(0, 10)) self.export_btn = ttk.Button( btn_frame, text="💾 导出结果", command=self.export_results, style="Success.TButton" ) self.export_btn.pack(side=tk.LEFT) # 按钮样式 self.style.configure("Accent.TButton", font=("Arial", 10, "bold"), foreground="white", background=self.colors["accent"]) self.style.configure("Warning.TButton", foreground="white", background=self.colors["warning"]) self.style.configure("Success.TButton", foreground="white", background=self.colors["success"]) # 状态栏 status_frame = ttk.Frame(search_frame) status_frame.pack(fill=tk.X, pady=(10, 0)) self.status_label = ttk.Label( status_frame, text="🟢 就绪", foreground=self.colors["success"], font=("Arial", 9, "bold") ) self.status_label.pack(side=tk.LEFT) self.progress = ttk.Progressbar( status_frame, orient="horizontal", length=100, mode="determinate", style="Custom.Horizontal.TProgressbar" ) self.progress.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=10) # 进度条样式 self.style.configure("Custom.Horizontal.TProgressbar", thickness=20, background=self.colors["accent"], troughcolor=self.colors["bg"]) self.stats_label = ttk.Label( status_frame, text="", foreground=self.colors["text"], font=("Arial", 9) ) self.stats_label.pack(side=tk.RIGHT) # 创建结果面板 results_frame = ttk.Frame(main_frame) results_frame.pack(fill=tk.BOTH, expand=True) # 创建结果面板(在搜索面板下方) results_frame = ttk.Frame(main_frame) results_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True, pady=10) # 修改为TOP布局 # 分割窗格(文件列表和预览左右排列) pane = ttk.PanedWindow(results_frame, orient=tk.HORIZONTAL) pane.pack(fill=tk.BOTH, expand=True) # 文件列表(左侧) file_frame = ttk.LabelFrame(pane, text="📂 搜索结果", padding=10) pane.add(file_frame, weight=1) # 文件预览(右侧) preview_frame = ttk.LabelFrame(pane, text="🔍 预览内容", padding=10) pane.add(preview_frame, weight=2) # 创建Treeview显示文件列表 file_tree_frame = ttk.Frame(file_frame) file_tree_frame.pack(fill=tk.BOTH, expand=True) # 定义树状视图列时添加fullpath列 columns = ("filename", "size", "modified", "fullpath") self.file_tree = ttk.Treeview( file_tree_frame, columns=columns, show="headings", selectmode="browse" ) # 配置列时隐藏fullpath列 self.file_tree.column("fullpath", width=0, stretch=False) # 隐藏列 # 配置Treeview样式 self.style.configure("Custom.Treeview", font=("Arial", 9), rowheight=25) self.style.configure("Custom.Treeview.Heading", font=("Arial", 9, "bold"), background=self.colors["accent"], foreground="white") # 配置列 self.file_tree.heading("filename", text="文件名", anchor="w") self.file_tree.heading("size", text="大小", anchor="center") self.file_tree.heading("modified", text="修改时间", anchor="w") self.file_tree.column("filename", width=250, anchor="w") self.file_tree.column("size", width=80, anchor="center") self.file_tree.column("modified", width=150, anchor="w") scroll_y = ttk.Scrollbar(file_tree_frame, orient="vertical", command=self.file_tree.yview) scroll_x = ttk.Scrollbar(file_tree_frame, orient="horizontal", command=self.file_tree.xview) self.file_tree.configure(yscrollcommand=scroll_y.set, xscrollcommand=scroll_x.set) self.file_tree.grid(row=0, column=0, sticky="nsew") scroll_y.grid(row=0, column=1, sticky="ns") scroll_x.grid(row=1, column=0, sticky="ew") file_tree_frame.rowconfigure(0, weight=1) file_tree_frame.columnconfigure(0, weight=1) self.preview_text = scrolledtext.ScrolledText( preview_frame, wrap=tk.WORD, font=("Consolas", 10), padx=10, pady=10, bg="#FFFFFF", fg=self.colors["text"], relief="flat" ) self.preview_text.pack(fill=tk.BOTH, expand=True) # 配置高亮标签 self.preview_text.tag_configure("highlight", background=self.colors["highlight"]) self.preview_text.tag_configure("match", background="#c8e6c9") self.preview_text.tag_configure("header", foreground=self.colors["success"], font=("Arial", 10, "bold")) self.preview_text.tag_configure("warning", foreground=self.colors["warning"], font=("Arial", 9, "italic")) self.preview_text.tag_configure("linenum", foreground="#7f8c8d") # 初始化变量 self.search_thread = None self.stop_requested = False self.results = {} self.all_files = [] # 绑定事件 self.file_tree.bind("<<TreeviewSelect>>", self.show_file_content) self.file_tree.bind('<Double-1>', self.open_selected_file) # 配置网格布局 results_frame.columnconfigure(0, weight=1) results_frame.rowconfigure(0, weight=1) def browse_directory(self): """浏览目录""" directory = filedialog.askdirectory(title="选择搜索目录") if directory: self.dir_entry.delete(0, tk.END) self.dir_entry.insert(0, directory) def start_search(self): """开始搜索""" # 重置状态 self.progress["value"] = 0 self.stop_requested = False self.results = {} self.all_files = [] # 清空UI for item in self.file_tree.get_children(): self.file_tree.delete(item) self.preview_text.delete("1.0", tk.END) # 获取参数 directory = self.dir_entry.get().strip() keyword = self.keyword_entry.get().strip() file_filter = self.filter_entry.get().strip() # 验证输入 if not directory or not os.path.isdir(directory): messagebox.showerror("错误", "请选择有效的搜索目录") return if not keyword: messagebox.showerror("错误", "请输入搜索关键词") return self.status_label.config(text="🟡 正在搜索...", foreground=self.colors["warning"]) self.search_btn.config(state=tk.DISABLED) self.stop_btn.config(state=tk.NORMAL) # 解析文件过滤器 filter_patterns = [pat.strip() for pat in file_filter.split(';')] if ';' in file_filter else [file_filter] # 编译搜索模式 flags = re.IGNORECASE if self.case_var.get() else 0 try: if self.regex_var.get(): pattern = re.compile(keyword, flags) else: pattern = re.compile(re.escape(keyword), flags) except re.error as e: messagebox.showerror("正则表达式错误", f"无效的正则表达式: {str(e)}") self.search_btn.config(state=tk.NORMAL) self.stop_btn.config(state=tk.DISABLED) return # 在后台线程搜索 self.search_thread = threading.Thread( target=self.perform_search, args=(directory, filter_patterns, pattern), daemon=True ) self.search_thread.start() def perform_search(self, directory, filter_patterns, pattern): """执行文件搜索""" try: # 收集所有匹配的文件 all_files = [] for root, _, files in os.walk(directory): if self.stop_requested: self.master.after(0, lambda: self.status_label.config( text="🟠 搜索已取消", foreground=self.colors["warning"])) return for file in files: file_path = os.path.join(root, file) # 检查文件大小限制(避免处理超大文件) try: file_size = os.path.getsize(file_path) limit = 100 * 1024 * 1024 # 100MB if file_size > limit: continue except: continue # 检查是否符合任一过滤模式 if any(fnmatch.fnmatch(file, pat) for pat in filter_patterns): all_files.append(file_path) self.all_files = all_files total_files = len(all_files) # 更新UI self.master.after(0, lambda: self.progress.configure(maximum=total_files)) self.master.after(0, lambda: self.stats_label.config(text=f"0/{total_files}")) # 搜索每个文件 self.results = {} processed = 0 matches_found = 0 for file_path in all_files: if self.stop_requested: break processed += 1 # 更新进度 self.master.after(0, lambda v=processed: self.progress.configure(value=v)) if processed % 10 == 0: # 每处理10个文件更新一次进度 self.master.after(0, lambda p=processed, t=total_files: self.stats_label.config(text=f"{p}/{t} ({p/t*100:.1f}%)")) # 忽略二进制文件(除非用户选择包含) if not self.binary_var.get() and self.is_binary(file_path): continue # 获取文件扩展名 _, ext = os.path.splitext(file_path) ext_lower = ext.lower() # 处理不同文件类型 if ext_lower in ['.docx', '.xlsx', '.xls', '.xlsm', '.pptx', '.pdf', '.doc']: matches = self.search_in_office_file(file_path, pattern) elif ext_lower in ['.zip', '.rar', '.7z', '.tar', '.gz']: matches = self.search_in_archive(file_path, pattern) else: matches = self.search_in_text_file(file_path, pattern) if matches: self.results[file_path] = matches matches_found += len(matches) # 获取文件信息 file_name = os.path.basename(file_path) try: size = os.path.getsize(file_path) size_str = f"{size/1024:.1f}KB" if size < 1024*1024 else f"{size/(1024*1024):.1f}MB" mod_time = time.ctime(os.path.getmtime(file_path)) except: size_str = "N/A" mod_time = "N/A" # 在UI线程中添加文件到列表 self.master.after(0, lambda fp=file_path, fn=file_name, sz=size_str, mt=mod_time: self.file_tree.insert("", "end", values=(fn, sz, mt, fp))) # 更新完成状态 if self.stop_requested: status_text = f"🟠 搜索已取消 - 找到 {len(self.results)} 个文件, {matches_found} 个匹配项" else: status_text = f"🟢 搜索完成 - 找到 {len(self.results)} 个文件, {matches_found} 个匹配项" self.master.after(0, lambda: self.status_label.config(text=status_text, foreground=self.colors["success"])) self.master.after(0, lambda: self.stats_label.config(text=f"已处理 {processed}/{total_files} 文件")) self.master.after(0, lambda: self.progress.configure(value=total_files)) except Exception as e: # 记录详细错误日志 error_info = f"搜索错误: {type(e).__name__} - {str(e)}" print(error_info) with open("search_errors.log", "a") as log: log.write(f"{time.strftime('%Y-%m-%d %H:%M:%S')} - {error_info}\n") import traceback traceback.print_exc(file=log) self.master.after(0, lambda: messagebox.showerror( "搜索错误", f"发生严重错误: {error_info}\n详细信息已记录到日志" )) finally: self.master.after(0, lambda: self.search_btn.config(state=tk.NORMAL)) self.master.after(0, lambda: self.stop_btn.config(state=tk.DISABLED)) self.search_thread = None def search_in_text_file(self, filepath, pattern): """在文本文件中搜索匹配项""" matches = [] try: encoding = self.detect_encoding(filepath) try: with open(filepath, 'r', encoding=encoding, errors='replace') as f: for line_num, line in enumerate(f, 1): if pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except UnicodeDecodeError: # 特殊编码处理回退 with open(filepath, 'rb') as f: content = f.read() try: text = content.decode('utf-8', errors='replace') except: text = content.decode('latin-1', errors='replace') for line_num, line in enumerate(text.splitlines(), 1): if pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except Exception as e: print(f"读取文本文件失败 {filepath}: {str(e)}") return matches def search_in_office_file(self, filepath, pattern): """在Office文件中搜索文本内容""" matches = [] _, ext = os.path.splitext(filepath) ext_lower = ext.lower() try: # DOCX文件处理 if ext_lower == '.docx': doc = docx.Document(filepath) # 搜索段落 for i, para in enumerate(doc.paragraphs, 1): if para.text and pattern.search(para.text): matches.append((i, f"段落 {i}: {para.text[:100]}" + ("..." if len(para.text) > 100 else ""))) # 搜索表格 for table in doc.tables: for row_idx, row in enumerate(table.rows, 1): for cell_idx, cell in enumerate(row.cells, 1): if cell.text and pattern.search(cell.text): content = cell.text.strip() if len(content) > 100: content = content[:100] + "..." matches.append((row_idx, f"表格 行{row_idx}列{cell_idx}: {content}")) # XLSX/XLS文件处理 elif ext_lower in ('.xlsx', '.xls', '.xlsm'): # 处理新格式Excel文件 if ext_lower in ('.xlsx', '.xlsm'): wb = load_workbook(filepath, read_only=True, data_only=True) for sheet_name in wb.sheetnames: sheet = wb[sheet_name] for row_idx, row in enumerate(sheet.iter_rows(values_only=True), 1): for col_idx, cell in enumerate(row, 1): if cell is not None and pattern.search(str(cell)): cell_ref = f"{chr(64+col_idx)}{row_idx}" cell_value = str(cell).strip() if len(cell_value) > 100: cell_value = cell_value[:100] + "..." matches.append((row_idx, f"工作表 '{sheet_name}' 单元格 {cell_ref}: {cell_value}")) # 处理旧格式Excel文件 elif ext_lower == '.xls': wb = xlrd.open_workbook(filepath) for sheet_idx in range(wb.nsheets): sheet = wb.sheet_by_index(sheet_idx) for row_idx in range(sheet.nrows): for col_idx in range(sheet.ncols): cell = sheet.cell_value(row_idx, col_idx) if cell and pattern.search(str(cell)): cell_ref = f"{chr(65+col_idx)}{row_idx+1}" cell_value = str(cell).strip() if len(cell_value) > 100: cell_value = cell_value[:100] + "..." matches.append((row_idx+1, f"工作表 '{sheet.name}' 单元格 {cell_ref}: {cell_value}")) # PPTX文件处理 elif ext_lower == '.pptx': from pptx import Presentation ppt = Presentation(filepath) # 搜索幻灯片文本 for slide_idx, slide in enumerate(ppt.slides, 1): for shape in slide.shapes: if hasattr(shape, "text"): if shape.text and pattern.search(shape.text): content = shape.text.strip() if len(content) > 100: content = content[:100] + "..." matches.append((slide_idx, f"幻灯片 {slide_idx}: {content}")) # PDF文件处理 elif ext_lower == '.pdf': with open(filepath, 'rb') as f: pdf = PyPDF2.PdfReader(f) for page_num in range(len(pdf.pages)): page_text = pdf.pages[page_num].extract_text() if page_text and pattern.search(page_text): # 提取匹配内容 matches_found = [] for match in pattern.finditer(page_text): context = page_text[max(0, match.start()-20):match.end()+20] context = context.replace('\n', ' ').strip() matches_found.append(context) # 添加到结果 if matches_found: preview = "; ".join(matches_found[:3]) # 显示前3个匹配 if len(matches_found) > 3: preview += f" ... (+{len(matches_found)-3} 更多)" matches.append((page_num+1, f"页面 {page_num+1}: {preview}")) # 旧版DOC文件处理 elif ext_lower == '.doc': try: # 尝试使用antiword转换DOC为文本 result = subprocess.run(['antiword', filepath], capture_output=True, text=True, timeout=10) if result.returncode == 0: doc_text = result.stdout for line_num, line in enumerate(doc_text.split('\n'), 1): if line and pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except Exception: # 备用方法:使用python-doc处理 import win32com.client word = win32com.client.Dispatch("Word.Application") word.Visible = False doc = word.Documents.Open(filepath) doc_text = doc.Content.Text doc.Close() word.Quit() for line_num, line in enumerate(doc_text.split('\n'), 1): if line and pattern.search(line): cleaned_line = line.strip() if len(cleaned_line) > 150: cleaned_line = cleaned_line[:150] + "..." matches.append((line_num, cleaned_line)) except Exception as e: print(f"处理Office文件失败 {filepath}: {str(e)}") return matches def search_in_archive(self, filepath, pattern): """在压缩文件中搜索匹配项""" matches = [] _, ext = os.path.splitext(filepath) ext_lower = ext.lower() try: # ZIP文件处理 if ext_lower in ('.zip', '.jar', '.war'): with zipfile.ZipFile(filepath, 'r') as archive: for name in archive.namelist(): # 只处理文本文件和Office文档 if not name.endswith(('/')) and not self.is_binary(name): try: with archive.open(name) as file: content = file.read(4096) # 只读取前4KB # 尝试检测编码 result = chardet.detect(content) encoding = result['encoding'] if result['confidence'] > 0.7 else 'utf-8' # 解码内容并搜索 try: text_content = content.decode(encoding, errors='replace') if pattern.search(text_content): matches.append((name, f"压缩文件中的文件: {name}")) except: # 二进制内容搜索 if pattern.search(content): matches.append((name, f"压缩文件中的文件(二进制内容): {name}")) except Exception: continue # 其他压缩格式(需要外部工具) elif ext_lower in ('.rar', '.7z', '.tar', '.gz'): # 使用7zip命令行工具解压并搜索 temp_dir = tempfile.mkdtemp() try: subprocess.run(['7z', 'x', filepath, f'-o{temp_dir}'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True, timeout=60) # 递归搜索解压的目录 for root, _, files in os.walk(temp_dir): for file in files: full_path = os.path.join(root, file) _, file_ext = os.path.splitext(file) file_ext = file_ext.lower() # 只在文本/Office文件中搜索 if file_ext in ['', '.txt', '.py', '.java', '.c', '.cpp', '.h', '.html', '.xml', '.json', '.csv', '.docx', '.xlsx', '.pptx', '.pdf']: if file_ext in ['.docx', '.xlsx', '.pptx', '.pdf']: file_matches = self.search_in_office_file(full_path, pattern) else: file_matches = self.search_in_text_file(full_path, pattern) if file_matches: matches.append((file, f"压缩文件中的文件: {file}")) finally: shutil.rmtree(temp_dir, ignore_errors=True) except Exception as e: print(f"处理压缩文件失败 {filepath}: {str(e)}") return matches def detect_encoding(self, filepath): """改进的文件编码检测方法""" try: # 尝试读取文件前4KB进行编码检测 with open(filepath, 'rb') as f: raw_data = f.read(4096) # 使用chardet进行编码检测 result = chardet.detect(raw_data) # 优先使用检测到的编码,否则尝试常见编码 if result['confidence'] > 0.7: return result['encoding'] # 中文环境常用编码回退策略 common_encodings = ['utf-8', 'gbk', 'gb2312', 'gb18030', 'latin1'] for encoding in common_encodings: try: # 尝试解码验证 raw_data.decode(encoding, errors='strict') return encoding except UnicodeDecodeError: continue # 默认使用UTF-8 return 'utf-8' except Exception: return 'utf-8' def is_binary(self, filepath): """检查文件是否为二进制文件""" try: with open(filepath, 'rb') as f: chunk = f.read(1024) if b'\0' in chunk: # 空字节是二进制文件的标志 return True # 检查高字节值 if any(byte >= 0x80 for byte in chunk): return True return False except: return False def stop_search(self): """停止当前搜索""" self.stop_requested = True self.status_label.config(text="🟠 正在停止...", foreground=self.colors["warning"]) self.stop_btn.config(state=tk.DISABLED) def export_results(self): """导出搜索结果""" if not self.results: messagebox.showinfo("导出结果", "没有可导出的搜索结果") return file_path = filedialog.asksaveasfilename( title="保存搜索结果为", defaultextension=".csv", filetypes=[("CSV 文件", "*.csv"), ("文本文件", "*.txt")] ) if not file_path: return try: with open(file_path, 'w', encoding='utf-8') as f: # 写出CSV头部 f.write("文件路径,匹配行号,匹配内容\n") # 写出每项结果 for file, matches in self.results.items(): for line_num, match_content in matches: # 清理内容中的逗号 cleaned_content = match_content.replace('"', '""').replace(',', ';') f.write(f'"{file}",{line_num},"{cleaned_content}"\n') messagebox.showinfo("导出成功", f"搜索结果已保存到:\n{file_path}") except Exception as e: messagebox.showerror("导出错误", f"导出失败: {str(e)}") def show_file_content(self, event=None): """在预览区域显示文件内容""" # 获取选中的文件 selection = self.file_tree.selection() if not selection: return # 获取完整文件路径 selected_item = self.file_tree.selection()[0] filepath = self.file_tree.item(selected_item, "values")[3] # 索引3是完整路径 # 清空预览区域 self.preview_text.delete(1.0, tk.END) # 获取文件扩展名 _, ext = os.path.splitext(filepath) ext_lower = ext.lower() # 显示文件路径标题 self.preview_text.insert(tk.END, f"文件路径: {filepath}\n", "header") # 处理不同文件类型 try: # 处理Office文档 if ext_lower in ['.docx', '.xlsx', '.xls', '.xlsm', '.pptx', '.pdf', '.doc']: matches = self.results.get(filepath, []) if not matches: self.preview_text.insert(tk.END, "\n未找到匹配内容\n", "warning") return self.preview_text.insert(tk.END, f"\n找到 {len(matches)} 个匹配项:\n\n", "header") # 显示每个匹配项 for i, (line_num, content) in enumerate(matches, 1): self.preview_text.insert(tk.END, f"[匹配项 {i}] 位置: {line_num}\n") self.preview_text.insert(tk.END, f"{content}\n\n") # 处理压缩文件 elif ext_lower in ['.zip', '.rar', '.7z', '.tar', '.gz']: matches = self.results.get(filepath, []) if not matches: self.preview_text.insert(tk.END, "\n未找到匹配内容\n", "warning") return self.preview_text.insert(tk.END, f"\n找到 {len(matches)} 个匹配项:\n\n", "header") for i, (file_in_zip, content) in enumerate(matches, 1): self.preview_text.insert(tk.END, f"[匹配项 {i}] 文件: {file_in_zip}\n") self.preview_text.insert(tk.END, f"{content}\n\n") # 处理文本文件 else: # 获取关键词高亮模式 keyword = self.keyword_entry.get().strip() flags = re.IGNORECASE if self.case_var.get() else 0 if self.regex_var.get(): try: pattern = re.compile(keyword, flags) except: pattern = None else: pattern = re.compile(re.escape(keyword), flags) # 显示文件内容并高亮匹配 self.preview_text.insert(tk.END, "\n文件内容:\n\n", "header") # 限制预览内容大小(最多显示1000行) max_preview_lines = 1000 try: encoding = self.detect_encoding(filepath) with open(filepath, 'r', encoding=encoding, errors='replace') as f: line_count = 0 for line in f: line_count += 1 if line_count > max_preview_lines: self.preview_text.insert(tk.END, f"\n... (文件过大,仅显示前{max_preview_lines}行)\n", "warning") break # 插入行号 self.preview_text.insert(tk.END, f"{line_count:4d} | ", "linenum") # 插入行内容并高亮匹配 if pattern: start_idx = 0 for match in pattern.finditer(line): # 插入匹配前的文本 self.preview_text.insert(tk.END, line[start_idx:match.start()]) # 插入高亮的匹配文本 self.preview_text.insert(tk.END, match.group(), "match") start_idx = match.end() # 插入匹配后的文本 self.preview_text.insert(tk.END, line[start_idx:]) else: self.preview_text.insert(tk.END, line) except UnicodeDecodeError: self.preview_text.insert(tk.END, "\n无法解码此文件内容(可能是二进制文件)\n", "warning") except Exception as e: self.preview_text.insert(tk.END, f"\n读取文件时出错: {str(e)}\n", "warning") except Exception as e: self.preview_text.insert(tk.END, f"\n加载文件内容出错: {str(e)}\n", "warning") def open_selected_file(self, event=None): """用系统默认程序打开选中的文件""" selection = self.file_tree.selection() if not selection: return selected_item = selection[0] filepath = self.file_tree.item(selected_item, "values")[3] try: if sys.platform == "win32": os.startfile(filepath) elif sys.platform == "darwin": # macOS subprocess.run(["open", filepath]) else: # Linux subprocess.run(["xdg-open", filepath]) except Exception as e: messagebox.showerror("打开文件失败", f"无法打开文件: {str(e)}") def open_file_location(self): """在文件资源管理器中打开文件所在位置""" selection = self.file_tree.selection() if not selection: return selected_item = selection[0] filepath = self.file_tree.item(selected_item, 'values')[1] folder = os.path.dirname(filepath) try: if sys.platform == "win32": subprocess.run(["explorer", "/select,", filepath]) elif sys.platform == "darwin": # macOS subprocess.run(["open", "-R", filepath]) else: # Linux subprocess.run(["xdg-open", folder]) except Exception as e: messagebox.showerror("打开位置失败", f"无法打开位置: {str(e)}") def show_file_context_menu(self, event): """显示文件列表的右键菜单""" item = self.file_tree.identify_row(event.y) if item: self.file_tree.selection_set(item) self.file_menu.tk_popup(event.x_root, event.y_root) def copy_selected_text(self): """复制预览区域中选中的文本""" selected_text = self.preview_text.get(tk.SEL_FIRST, tk.SEL_LAST) if selected_text: self.master.clipboard_clear() self.master.clipboard_append(selected_text) # 程序入口 if __name__ == "__main__": root = tk.Tk() app = EnhancedFileSearchApp(root) # 添加图标(如果有) try: if sys.platform == "win32": root.iconbitmap("search_icon.ico") else: img = tk.PhotoImage(file="search_icon.png") root.iconphoto(True, img) except: pass root.mainloop() 搜索结果与搜索选项之间的间隔太大了,特别是我全屏的时候,中间空出一大片
09-13
<think> 根据您的描述,问题在于搜索结果面板和搜索选项面板之间的间隔过大。这是因为在布局中,搜索面板(search_frame)和结果面板(results_frame)都是垂直排列(TOP),但是它们之间没有设置合理的伸缩关系。另外,结果面板的高度可能没有充分利用空间。 解决方案: 1. 调整主框架(main_frame)的内部布局权重,让结果面板占据更多空间。 2. 确保搜索面板只占用必要的高度,而结果面板填充剩余空间。 修改步骤: - 移除不必要的pady设置,特别是搜索面板下方的pady=(0,10)和结果面板上方的pady=10。 - 使用grid布局或者pack布局时,设置expand和weight属性,让结果面板可以扩展。 但是,在pack布局中,我们可以通过设置side和fill来控制。目前我们已经将search_frame放在TOP,results_frame也放在TOP。问题在于两个框架之间的间隔过大。 我们可以将主框架(main_frame)分成两个部分: 顶部:搜索面板(search_frame),高度固定(或者由内部组件决定) 底部:结果面板(results_frame),填充剩余空间 修改方法: 保持search_frame的pack方式:side=TOP, fill=X 修改results_frame的pack方式:side=TOP, fill=BOTH, expand=True 同时,调整search_frame的pady设置,减少与下方结果面板的间距。 具体修改代码位置: 在创建search_frame的pack语句中,将pady=(0,10)改为pady=(0,5)或更小。 在创建results_frame的pack语句中,设置expand=True, fill=BOTH,并调整pady。 另外,请注意,在代码中您两次创建了results_frame(重复打包),这可能会导致布局问题。我们在下面的代码中修正。 修正重复创建results_frame的问题: 在原始代码中,您创建了两个results_frame,第二个覆盖了第一个: 第一次:results_frame = ttk.Frame(main_frame) 并打包 第二次:results_frame = ttk.Frame(main_frame) 并再次打包 修正:只创建一次results_frame。 修改后的布局代码部分: ```python # 创建搜索面板(顶部) search_frame = ttk.LabelFrame( main_frame, text="⚙️ 搜索选项", padding=(15, 10), style="Search.TLabelframe" ) # 修改pady,减少底部间距 search_frame.pack(side=tk.TOP, fill=tk.X, padx=0, pady=(0, 5)) # 修改这里:pady=(0,5) # ... 搜索面板内部代码 ... # 创建结果面板(在搜索面板下方) results_frame = ttk.Frame(main_frame) # 让结果面板填充剩余空间 results_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True, pady=0) # 修改这里:pady=0 # 分割窗格(文件列表和预览左右排列) pane = ttk.PanedWindow(results_frame, orient=tk.HORIZONTAL) pane.pack(fill=tk.BOTH, expand=True) ``` 另外,为了确保结果面板内部的分割窗格(pane)能够充分利用空间,我们设置pane的fill和expand属性为True。 同时,检查文件列表和预览框的填充情况。 如果仍然有过多空白,可能是由于Treeview和预览文本框的父容器没有正确设置expand和fill。确保: - file_tree_frame 和 preview_text 的父容器(file_frame和preview_frame)被设置为可以扩展。 - 在pane.add时,设置weight属性(已经设置)。 另外,我们还可以考虑设置主窗口的最小高度,或者允许用户调整分割窗格的比例。 最后,如果全屏时仍然有空白,可能是因为窗口扩大后,内部组件没有随之扩展。我们通过设置各个层级的expand和fill来确保。 我们修改以下部分: 1. 搜索面板:只占用必要高度,紧贴上方。 2. 结果面板:填充剩余空间,且内部的分割窗格也扩张。 由于您已经提供了代码,我将直接修改布局部分的pack参数。 注意:在您的代码中,有两个results_frame的pack语句,我删除了第一个,只保留第二个(并且修改了它的pack参数)。 下面是修改后的相关代码部分(仅布局部分): 原始代码(有重复): # 创建结果面板 results_frame = ttk.Frame(main_frame) results_frame.pack(fill=tk.BOTH, expand=True) # 创建结果面板(在搜索面板下方) results_frame = ttk.Frame(main_frame) results_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True, pady=10) # 修改为TOP布局 修改后(只保留一个results_frame,并调整pady): # 创建结果面板(在搜索面板下方) results_frame = ttk.Frame(main_frame) results_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True, pady=0) # 去掉垂直间距 同时,搜索面板的打包改为pady=(0,5): search_frame.pack(side=tk.TOP, fill=tk.X, padx=0, pady=(0, 5)) # 底部间距为5像素 这样两个面板之间的间距就很小了。 另外,为了确保分割窗格(pane)能够扩展,我们已经设置: pane.pack(fill=tk.BOTH, expand=True) 这样应该可以解决间距过大的问题。 但是,请注意,如果搜索面板内部组件过多,高度可能会很高,那么留给结果面板的空间就会变少。不过您的搜索面板高度是固定的(由内部组件决定)。 如果问题依旧,可以考虑将主窗口的初始高度增大,或者允许用户调整窗口大小时内部组件自动调整。 由于无法实际看到界面,以上调整是基于Tkinter布局经验。如果还有问题,可能需要进一步调整。 现在,根据上述修改,调整代码。 注意:由于您提供的代码较长,我只修改了布局部分的相关行。以下是修改后的完整类初始化代码(仅布局部分): </think> 您的观察很到位。问题在于搜索结果面板和搜索选项面板之间的间距过大,特别是在全屏模式下。这主要是由于布局设置和间距配置不当造成的。让我帮您优化布局,减少不必要的间距,使界面更加紧凑美观。 ### 问题分析: 1. **垂直间距过大**:`pady=(0, 10)` 和 `pady=10` 设置导致搜索选项和结果面板之间有过多的垂直空间 2. **布局权重分配不合理**:结果面板没有充分利用可用空间 3. **不必要的容器嵌套**:增加了额外的层级和间距 ### 解决方案: 我将优化布局结构,减少不必要的间距,并确保全屏模式下空间得到充分利用: ```python class EnhancedFileSearchApp: def __init__(self, master): # ... [前面的代码保持不变] ... # 设置主窗口背景色 master.configure(bg=self.colors["bg"]) # 创建主框架 - 使用grid布局更精确控制 main_frame = ttk.Frame(master) main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5) # 减少主框架外边距 # 创建搜索面板(顶部) search_frame = ttk.LabelFrame( main_frame, text="⚙️ 搜索选项", padding=(10, 8), # 减少内边距 style="Search.TLabelframe" ) search_frame.grid(row=0, column=0, sticky="ew", padx=0, pady=(0, 5)) # 减少下边距 # 搜索目录 - 优化布局减少垂直空间 dir_frame = ttk.Frame(search_frame) dir_frame.pack(fill=tk.X, pady=3) # 减少垂直间距 # ... [搜索目录组件保持不变] ... # 关键词 - 优化布局 kw_frame = ttk.Frame(search_frame) kw_frame.pack(fill=tk.X, pady=3) # 减少垂直间距 # ... [关键词组件保持不变] ... # 文件过滤 - 优化布局 filter_frame = ttk.Frame(search_frame) filter_frame.pack(fill=tk.X, pady=3) # 减少垂直间距 # ... [文件过滤组件保持不变] ... # 搜索选项 - 优化布局 options_frame = ttk.Frame(search_frame) options_frame.pack(fill=tk.X, pady=5) # 适当间距 # ... [选项组件保持不变] ... # 按钮组 - 优化布局 btn_frame = ttk.Frame(search_frame) btn_frame.pack(fill=tk.X, pady=(8, 5)) # 减少垂直间距 # ... [按钮组件保持不变] ... # 状态栏 - 优化布局 status_frame = ttk.Frame(search_frame) status_frame.pack(fill=tk.X, pady=(5, 0)) # 减少上边距 # ... [状态组件保持不变] ... # 创建结果面板 - 使用grid放置在下层 results_frame = ttk.Frame(main_frame) results_frame.grid(row=1, column=0, sticky="nsew", pady=0) # 无垂直间距 # 配置网格权重 - 结果面板占据剩余空间 main_frame.rowconfigure(1, weight=1) main_frame.columnconfigure(0, weight=1) # 分割窗格(文件列表和预览左右排列) pane = ttk.PanedWindow(results_frame, orient=tk.HORIZONTAL) pane.pack(fill=tk.BOTH, expand=True, padx=0, pady=0) # 无内边距 # 文件列表(左侧) file_frame = ttk.LabelFrame( pane, text="📂 搜索结果", padding=(8, 6) # 减少内边距 ) pane.add(file_frame, weight=1) # 文件预览(右侧) preview_frame = ttk.LabelFrame( pane, text="🔍 预览内容", padding=(8, 6) # 减少内边距 ) pane.add(preview_frame, weight=2) # 创建Treeview显示文件列表 file_tree_frame = ttk.Frame(file_frame) file_tree_frame.pack(fill=tk.BOTH, expand=True, padx=0, pady=0) # 无内边距 # ... [Treeview代码保持不变] ... self.preview_text = scrolledtext.ScrolledText( preview_frame, wrap=tk.WORD, font=("Consolas", 10), padx=8, # 减少内边距 pady=8, # 减少内边距 bg="#FFFFFF", fg=self.colors["text"], relief="flat" ) self.preview_text.pack(fill=tk.BOTH, expand=True, padx=0, pady=0) # ... [其余代码保持不变] ... ``` ### 关键优化点: 1. **布局结构调整**: - 使用 `grid` 布局替代部分 `pack` 布局,更精确控制位置 - 添加 `rowconfigure` 和 `columnconfigure` 使结果面板占据大部分空间 2. **间距优化**: - 减少所有 `pady` 和 `padx` 值(平均减少30-50%) - 搜索选项面板底部间距从10px减少到5px - 移除面板之间的额外空白区域 3. **内边距优化**: - 减少标签框架的内边距(padding) - 减少文本框和树状视图的内边距 4. **权重分配**: - 确保结果面板占据大部分垂直空间 - 文件列表和预览面板按权重分配水平空间(1:2比例) 5. **全屏优化**: - 所有组件设置为可扩展,充分利用全屏空间 - 移除固定高度限制,使用权重分配空间 ### 其他改进建议: 如果您希望进一步优化界面,可以考虑: 1. **添加可折叠面板**: ```python # 在__init__方法中添加 self.search_options_expanded = tk.BooleanVar(value=True) # 添加折叠/展开按钮 toggle_btn = ttk.Button( search_frame, text="▲", command=self.toggle_search_options, width=2 ) toggle_btn.pack(side=tk.RIGHT, padx=(0, 5)) # 添加折叠方法 def toggle_search_options(self): expanded = self.search_options_expanded.get() if expanded: # 隐藏选项组件 self.dir_frame.pack_forget() self.kw_frame.pack_forget() self.filter_frame.pack_forget() self.options_frame.pack_forget() self.toggle_btn.config(text="▼") else: # 显示选项组件 self.dir_frame.pack(fill=tk.X, pady=3) self.kw_frame.pack(fill=tk.X, pady=3) self.filter_frame.pack(fill=tk.X, pady=3) self.options_frame.pack(fill=tk.X, pady=5) self.toggle_btn.config(text="▲") self.search_options_expanded.set(not expanded) ``` 2. **添加动态分隔条**: ```python # 在创建pane后添加 pane.bind("<ButtonPress-1>", self.on_sash_press) pane.bind("<B1-Motion>", self.on_sash_drag) def on_sash_press(self, event): self.sash_pos = pane.sash_coord(0)[0] def on_sash_drag(self, event): pane.sash_place(0, event.x, pane.sash_coord(0)[1]) ``` 3. **添加状态保存**: ```python # 在__init__方法中添加 self.geometry_file = "window_geometry.txt" self.load_geometry() # 添加方法 def load_geometry(self): try: with open(self.geometry_file, "r") as f: self.master.geometry(f.read()) except: self.master.geometry("1200x800") def save_geometry(self): with open(self.geometry_file, "w") as f: f.write(self.master.geometry()) # 在关闭窗口时保存 self.master.protocol("WM_DELETE_WINDOW", self.on_close) def on_close(self): self.save_geometry() self.master.destroy() ``` 这些优化将使您的界面在全屏时更加紧凑高效,同时保持良好的用户体验。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值