原创水题:Fish 笑传之查查补
题目描述
在计算机科学中,Shell 是指一种提供用户与操作系统交互的界面程序。之所以称其为"壳",是因为它起到"封装"操作系统底层(即 Kernel)的作用,屏蔽用户直接与内核的交互,同时管理用户的命令输入和操作系统的响应。

Shell 通常分为命令行 Shell 和图形 Shell。如 Windows 中的"文件资源管理器"就是一个典型的图形 Shell,允许用户通过图形界面与系统交互。但狭义的 Shell 单指前者,本题中的 Shell 指的是命令行 Shell。
在 Windows 系统中,常见的 Shell 包括 cmd.exe(命令提示符)和 PowerShell。而在 UNIX 系统及其衍生系统中,Shell 的种类更加丰富。以下是几种经典的 UNIX Shell:
sh(Bourne Shell):最初的 UNIX Shell,也是最为经典的版本。ash(Almquist Shell):优化了性能,广泛用于 BSD 系统。bash(Bourne-Again Shell):借鉴了多个 Shell 的功能,成为大多数 Linux 系统的默认 Shell。zsh(Z Shell):对sh做了许多改进,并引入其他 Shell 的特性,自 2019 年起成为 macOS 的默认 Shell。fish(Friendly Interactive Shell):旨在提供更友好的交互体验,拥有智能的命令补全功能,适用于大部分平台。
其中,fish 以其智能、高效的命令补全功能闻名。它不仅能根据可执行文件的名称进行自动补全,还可以基于用户定义的脚本、上下文环境以及历史命令进行动态补全,极大提升了用户的工作效率。下面展示了基于可执行文件和历史命令的补全示例:



Charles 非常喜欢使用 fish,并决定实现一个简化版的动态补全引擎,帮助他在日常工作中快速完成指令补全。这个引擎基于一组预定义的补全规则,能够根据用户输入的前缀迅速返回所有匹配的补全结果。为了满足不同场景的需求,补全规则分为两种类型:
- 全局规则:适用于所有上下文的补全。
- 上下文规则:仅在特定上下文下生效的补全规则。
每条补全规则由以下部分组成:
- 规则类型:全局规则
global或上下文规则context。 - 触发条件(仅适用于上下文规则):规则的适用范围,例如特定命令(如
git)或路径(如/home/charles/)。 - 补全词列表:该规则提供的补全词。
Charles 的引擎需要支持以下功能:
- 根据用户输入的前缀,结合所有匹配的规则,输出补全结果。
- 当全局规则和上下文规则冲突时(即同一前缀的补全结果不同),优先选择上下文规则。
你需要帮助 Charles 实现他的补全引擎,并完成一系列动态补全任务。
输入描述
第一行包含两个整数 和 ,分别表示补全规则数量和查询数量。
接下来包含 条补全规则,每条补全规则的格式如下:
- 第一行为规则类型,可能是全局规则
global或上下文规则context。 - 如果规则是
context类型,接下来一行包含触发条件(一个字符串,表示上下文)。 - 接着是补全词列表:第一行是整数 ,表示补全词的个数,紧接着是 个以空格分隔的补全词。
接下来 行,每行包含一个查询,格式为 上下文 前缀。
其中,若上下文为 . 则表示无上下文(仅适用全局规则),前缀为用户输入的非空字符串。
保证输入的字符串长度均不超过 ,不同规则的触发条件可能相同,补全词列表中的词也可能重复。
输出描述
对于每个查询,输出一行,包含去重后的补全结果,按照字典序排列并以空格分隔。
如果没有匹配的补全结果,或输入的命令已经是完整的且无法进行下一步补全,输出 EMPTY。
样例
- 输入数据 1
4 6
global
3 ls cd echo
context
git
4 add addon commit push
context
/home/charles/
2 work personal
global
2 help exit
. l
git add
git addon
/home/charles/ p
/home/charles/ w
/home/frex/ x
- 输出数据 1
ls
add addon
EMPTY
personal
work
EMPTY
- 输入数据 2
2 5
global
6 a ab abc b bc bcd
context
echo
3 a ac ak
echo a
echo ac
echo b
. b
ls a
- 输出数据 2
a ac ak
EMPTY
EMPTY
b bc bcd
a ab abc
提示
样例解释
- 对于样例 :
-
规则解析:
- 第一条规则为全局规则,提供的补全词是:
ls,cd,echo。 - 第二条规则为上下文规则,适用于
git命令,补全词是:add,addon,commit,push。 - 第三条规则为上下文规则,适用于路径
/home/charles/,补全词是:work,personal。 - 第四条规则为全局规则,提供的补全词是:
help,exit。
- 第一条规则为全局规则,提供的补全词是:
-
查询解析:
- 查询
. l:- 没有上下文,只有全局规则适用。全局规则提供了补全词
cd,echo,exit,help,ls。 l前缀匹配补全词ls,所以输出:ls。
- 没有上下文,只有全局规则适用。全局规则提供了补全词
- 查询
git add:- 上下文为
git,适用第二条规则,提供补全词:add,addon,commit,push。 add前缀匹配补全词add和addon,所以输出:add addon。
- 上下文为
- 查询
git addon:- 上下文为
git,适用第二条规则,提供补全词:add,addon,commit,push。 addon已经是完整的命令,无法继续补全,输出:EMPTY。
- 上下文为
- 查询
/home/charles/ p:- 上下文为
/home/charles/,适用第三条规则,提供补全词:personal,work。 p前缀匹配补全词personal,所以输出:personal。
- 上下文为
- 查询
/home/charles/ w:- 上下文为
/home/charles/,适用第三条规则,提供补全词:personal,work。 w前缀匹配补全词work,所以输出:work。
- 上下文为
- 查询
/home/frex/ x:- 上下文为
/home/frex/,没有对应的上下文规则,只有全局规则适用。全局规则提供了补全词cd,echo,exit,help,ls。 - 没有与
x匹配的补全词,无法进行补全,输出:EMPTY。
- 上下文为
- 查询
-
- 对于样例 :
-
规则解析:
- 第一条规则为全局规则,提供的补全词是:
a,ab,abc,b,bc,bcd。 - 第二条规则为上下文规则,适用于
echo命令,补全词是:a,ac,ak。
- 第一条规则为全局规则,提供的补全词是:
-
查询解析:
- 查询
echo a:- 上下文为
echo,适用第二条规则,提供补全词:a,ac,ak。 a前缀匹配补全词a,ac,ak,所以输出:a ac ak。
- 上下文为
- 查询
echo ac:- 上下文为
echo,适用第二条规则,提供补全词:a,ac,ak。 ac已经是完整的命令,无法继续补全,输出:EMPTY。
- 上下文为
- 查询
echo b:- 上下文为
echo,适用第二条规则,提供补全词:a,ac,ak。 - 没有与
b匹配的补全词,无法进行补全,输出:EMPTY。
- 上下文为
- 查询
. b:- 没有上下文,只有全局规则适用。全局规则提供了补全词
a,ab,abc,b,bc,bcd。 b前缀匹配补全词b,bc,bcd,所以输出:b bc bcd。
- 没有上下文,只有全局规则适用。全局规则提供了补全词
- 查询
ls a:- 上下文为
ls,没有对应的上下文规则,只有全局规则适用。全局规则提供了补全词a,ab,abc,b,bc,bcd。 a前缀匹配补全词a,ab,abc,所以输出:a ab abc。
- 上下文为
- 查询
-
思路
思路?你怕不是在说笑吧?
这么大份的题我怎么可能会有思路?
这道题其实是一个经典的字典树 Trie 的简化版本。
核心的数据结构包括:
context:map<string, set<string>>- 存储每个上下文对应的单词集合;-
global:set<string>- 存储全局单词集合(默认上下文)。
补全词去重的工作交给set<string>完成就好。
题解
#include <bits/stdc++.h>
int main() {
int R, Q;
std::cin >> R >> Q;
std::map<std::string, std::set<std::string>> context;
std::set<std::string> global;
while (R--) {
std::string rule;
std::cin >> rule;
if (rule == "context") {
std::string condition;
std::cin >> condition;
int n;
std::cin >> n;
for (int i = 0; i < n; i++) {
std::string word;
std::cin >> word;
context[condition].emplace(word);
}
} else {
int n;
std::cin >> n;
for (int i = 0; i < n; i++) {
std::string word;
std::cin >> word;
global.emplace(word);
}
}
}
while (Q--) {
std::string con, pre;
std::cin >> con >> pre;
std::vector<std::string> ans;
if (context.count(con)) {
for (auto s : context[con]) {
if (s.size() >= pre.size() && s.substr(0, pre.size()) == pre) {
ans.emplace_back(s);
}
}
} else {
for (auto s : global) {
if (s.size() >= pre.size() && s.substr(0, pre.size()) == pre) {
ans.emplace_back(s);
}
}
}
if (ans.empty() || ans.size() == 1 && ans[0] == pre) {
std::cout << "EMPTY
";
} else {
for (int i = 0; i < ans.size(); i++) {
std::cout << ans[i] << "
"[i + 1 == ans.size()];
}
}
}
}