UVa12298 3KP-BASH Project

UVa12298 3KP-BASH Project

题目链接

UVa12298 3KP-BASH Project

题意

摘自 《算法竞赛入门经典:训练指南》刘汝佳,陈锋著。有删改。

你的任务是为一个假想的 3KP 操作系统编写一个简单的 Bash 模拟器。由于操作系统本身还没有写完,你暂时需要在模拟器的内存中虚拟出一个文件系统,而不是和真实的文件系统交互。下面介绍 3KP 操作系统中的文件系统。

文件(file)是数据存储的最小单位。文件名由英文字母(大小写敏感)、数字和点(.)组成,但不能包含两个连续的点。用户创建的文件名不能是单个的点字符(它代表当前目录)。文件名长度不能超过 255 ,文件大小不能超过 2 63 2^{63} 263。一个文件可能具有 directory 属性和 hidden 属性。

目录(directory)是大小为 0 、具有 directory 属性的文件,里面可以保存任意数量的目录和文件。空文件系统只有一个文件,叫做"根目录"。在任意时刻,有一个称为"当前目录"的目录。 Bash 启动时,当前目录就是根目录。

指令中引用文件的时候,可以使用绝对路径或者相对路径。绝对路径以字符/开头,如 /home/acm/uva ;相对路径不以字符 / 开头,当前目录和上层目录分别用 . 和 ... 表示,如当前目录为 /home/acm/uva,那么相对路径 .../.../.../home/... 就是根目录。 你的 Bash 模拟器需要支持 8 个命令,具体如下表所示。

用法 描述
cd path 改变当前目录。path 可以是相对路径,也可以是绝对路径。如果目录不存在或名称不合法,输出 path not found 。
touch filename [-size] [-h] 修改文件。文件名之前的部分应当是文件系统中存在的目录,否则输出 path not found 。filename 的最后一部分应该是合法的文件名,否则输出 bad usage 。在正常情况下,同名文件(如果有的话)应当先被删除,然后新建文件。文件的大小由 -size 参数给出(默认为0),参数-h表示创建隐藏文件。如果存在同名的目录,输出 a dretory with the same name exists 。用户不会指定多个 -size 参数。
mkdir path [-h] 创建目录。文件名之前的部分应当是文件系统中存在的目录,否则输出 path not found。path 的最后一部分应该是合法的文件名,否则输出 bad usage。参数 -h 表示创建隐藏目录。如果存在一个同名目录或文件,输出 file or directory with the same name exists 。
find filename [-r] [-h] 查找目录或文件。filename的前部应当是文件系统中存在的目录,否则输出 path not found。filename 的最后一部分应该是合法的文件名(否则按找不到处理),表示查找的目录或者文件的名称。-r 参数表示当前目录下的所有子目录也要查找。默认情况下,find 命令在显示结果时会忽略掉隐藏文件(但会在隐藏目录中进行查找), 如果加了 -h 参数,则会连隐藏文件一起显示。如果没有一个需要输出的文件,输出 file not found 。否则对于找到的每个文件,输出单独的一行,依次包含带绝对路径的文件名、文件大小,以及 hidden (如果有隐藏属性)、 dir (如果是目录)。这些文件应当按照带绝对路径的文件名的字典序从小到大输出。
ls [path] [-h] [-r] [-s] [-S] [-f] [-d] 没有参数的情况下,Is 命令列出当前目录的所有非隐藏文件(格式同find)。如果没有一个需要输出的内容,输出 [empty]。如果path是文件系统中存在的目录,则列出该目录(而非当前目录)中的文件。如果指定了 path 參数但 path 不是一个合法的路径,则输出 path not found 。 -h 表示要列出隐藏文件(默认不显示),-r 表示递归列出所有子目录的文件(即使未指定 -h 也需要进入隐藏目录递归),-s 表示按照文件大小的非降序排列(文件大小相同的情况仍按照带绝对路径的文件名的字典序排列),-S 表示按照文件大小的非升序排列(如文件大小相同则参见 -s),-d 表示只列出目录,-f 表示只列出非目录。用户不会同时指定 -s 和 -S 。
pwd 输出当前目录的绝对路径
exit 退出 Bash
grep "string" 本题中,grep 只能通过管道的形式调用,即 command1 | grep"string",表示先执行 command1,然后在命令输出结果中搜索字符串"string",按照原顺序输出所有包含这个字符串的行。比如,前一个命令的输出是 path not found,那么 grep"ot fou"就应该也输出 path not found。用户输入的 string 参数保证在一对引号内,且内部不含引号,也没有转义符

命令行包含一条或多条命令,用管道符号 | 隔开(管道符号的前后不一定有空格)。第一条命令必须是除了 grep 之外的上述命令之一,而剩下的命令必须是 grep。如果违反上述规定,应输出 bad usage,并且不执行任何命令。输入不会在除了 grep 之外的其他命令中使用引号(原题未写,但书中是这么说的)。

命令和参数、参数和参数之间用一个或多个空格隔开。每个命令的必选参数(如果有的话)不一定是第一个参数,可选参数可能在必选参数之前,且以任意顺序出现。如果用户忽略了必选参数,或传入了过多必选参数,应输出 bad usage 。但忽略传入的无效可选参数(如 mkdir a -h -h -h -h -abababab 等价于 mkdir a -h)。

输入格式

输入包含多组数据,每组数据对应一次独立的 BASH 会话。数据的每行为一行命令(长度不超过 2048 个字符)。命令的参数之间可以有任意数量的空白字符。每次会话的最后一条命令保证为exit。输入结束标志为文件结束符(EOF)。假定每次会话开始时,文件系统为空,当前目录为根目录。

输出格式

依次输出每行命令的输出(如果有的话)。

分析

纯模拟题,很难 AC,洛谷题解上 ExplodingKonjac 说他耗时 346 天通过此题。贴上 ExplodingKonjac 的补充说明:

文件名中只能包含大小写英文字母、数字、点号,且不可以是单个点号,也不能包含两个连续点号,长度不能超过 255 。这句在题面中有,但是容易被忽视。执行 touch 和 mkdir 时文件或目录名违反上述规定要输出 bad usage(但如果文件文件或目录名之前的路径如果不存在要输出 path not found)。

文件大小不能超过 2 63 2^{63} 263 ,违反该规定应当输出 bad usage。实测数据没有超过 2 63 2^{63} 263的,不过读取-size参数应该注意像 -100ch 这种要当数字提取成100,而 -ch100 这种不能当数字。

文件路径中两个斜杠之间可能是空的,这种东西表示当前路径。比如路径 .///././// 和路径 . 表示同一种东西。

一个命令行中有由管道符号隔开的多个命令时,应该首先判断第一个命令不是 grep(是 grep 则直接输出 bad usage)才执行第一个命令。之后的命令必须都是 grep,否则直接输出 bad usage 而不执行第二个命令及以后的所有命令 。 比如 mkdir a | tt | grep "cc"会先执行 mkdir a,然后输出 bad usage。

管道符号和空格可能被引号所包含,你不能根据其分隔参数或命令 。比如说,grep "ot fou" 不能被分隔成 grep、"ot、fou"。

ls 命令中可能同时包含 -d 和 -f 参数,此时应该输出 [empty]。但是数据中不会同时包含 -s 和 -S。

AC 代码

cpp 复制代码
#include <iostream>
#include <string>
#include <sstream>
#include <vector>
#include <algorithm>
using namespace std;

#define ULL unsigned long long

const string no_such_command = "no such command", bad_usage = "bad usage", path_not_found = "path not found",
             file_not_found = "file not found", dir_exists = "a directory with the same name exists",
             f_dir_exists = "file or directory with the same name exists", empty_l = "[empty]";
struct params {ULL size = 0; bool r = false, h = false, s = false, S = false, f = false, d = false;};
struct file {
    string name, full_path; int parent; ULL size; bool dir, hidden; vector<int> sub;
    file(const string& name, const string& full_path, int parent, ULL size = 0, bool dir = false, bool hidden = false)
    :name(name), full_path(full_path), parent(parent), size(size), dir(dir), hidden(hidden) {};
};
vector<file> fs; int curr_dir;

void trim(string& s) {
    int l, r, t = s.size();
    for(l=0; l<t; ++l) if(!isspace(s[l])) break;
    for(r=t-1; r>l; --r) if(!isspace(s[r])) break;
    s = s.substr(l, r-l+1);
}

bool valid_path(const string& s, int& dir, string& name, bool pre = true) {
    vector<string> p; dir = s[0] == '/' ? 0 : curr_dir;
    if (!s.empty() && s.back() == '/') pre = false;
    for (int i=0, h=0, t=s.size(); i<t; ++i) {
        if (s[i] == '/') {
            if (i > 0 && s[i-1] != '/') p.push_back(s.substr(h, i-h));
            h = i+1;
        } else if (i+1 == t) p.push_back(s.substr(h, t-h));
    }
    for (int i=0, t=p.size()-pre; i<t; ++i) {
        if (p[i] == ".") continue;
        if (p[i] == "..") {
            if (fs[dir].parent < 0) return false;
            dir = fs[dir].parent;
        } else {
            const vector<int>& sub = fs[dir].sub; bool ok = false;
            for (int j=sub.size()-1; j>=0; --j) if (fs[sub[j]].name == p[i]) {
                if (fs[sub[j]].dir) dir = sub[j], ok = true;
                break;
            }
            if (!ok) return false;
        }
    }
    name = p.empty() || s.back() == '/' ? "" : p.back();
    return true;
}

bool valid_name(const string& s) {
    if (s.empty() || s.size() > 255) return false;
    for (int i=0, t=s.size(); i<t; ++i)
        if ((s[i]=='.' && (t==1 || (s[i+1]=='.'))) || (s[i]!='.' && !isalpha(s[i]) && !isdigit(s[i]))) return false;
    return true;
}

bool cmp_p(int i, int j) {
    return fs[i].full_path < fs[j].full_path;
}

bool cmp_s(int i, int j) {
    return fs[i].size < fs[j].size || (fs[i].size == fs[j].size && fs[i].full_path < fs[j].full_path);
}

bool cmp_S(int i, int j) {
    return fs[i].size > fs[j].size || (fs[i].size == fs[j].size && fs[i].full_path < fs[j].full_path);
}

void visit_fs(int dir, vector<int>& out, const string& name, bool r, bool h, bool d = true, bool f = true) {
    for (int i=0, t=fs[dir].sub.size(); i<t; ++i) {
        const file& fi = fs[fs[dir].sub[i]];
        if ((!fi.hidden || h) && ((fi.dir && d) || (!fi.dir && f)) && (name.empty() || fi.name == name))
            out.push_back(fs[dir].sub[i]);
        if (fi.dir && r) visit_fs(fs[dir].sub[i], out, name, r, h, d, f);
    }
}

string info(const file& f) {
    ostringstream ss; ss << f.full_path << ' ' << f.size;
    if (f.hidden) ss << " hidden";
    if (f.dir) ss << " dir";
    return ss.str();
}

void new_session() {
    fs.clear(); fs.push_back(file("", "/", -1, 0, true)); curr_dir = 0;
}

void cd(const string& mp, vector<string>& out) {
    int dir; string name;
    if (!valid_path(mp, dir, name, false)) return out.push_back(path_not_found);
    curr_dir = dir;
}

void touch(const string& mp, const params& p, vector<string>& out) {
    int dir; string name;
    if (!valid_path(mp, dir, name)) return out.push_back(path_not_found);
    if (!valid_name(name)) return out.push_back(bad_usage);
    for (int i=fs[dir].sub.size()-1; i>=0; --i) if (fs[fs[dir].sub[i]].name == name) {
        if (fs[fs[dir].sub[i]].dir) return out.push_back(dir_exists);
        fs[fs[dir].sub[i]].size = p.size; fs[fs[dir].sub[i]].hidden = p.h;
        return;
    }
    file fi(name, (dir ? fs[dir].full_path + '/' : "/") + name, dir, p.size, false, p.h);
    int s = fs.size(); fs[dir].sub.push_back(s); fs.push_back(fi);
}

void mkdir(const string& mp, const params& p, vector<string>& out) {
    int dir; string name;
    if (!valid_path(mp, dir, name)) return out.push_back(path_not_found);
    if (!valid_name(name)) return out.push_back(bad_usage);
    for (int i=fs[dir].sub.size()-1; i>=0; --i) if (fs[fs[dir].sub[i]].name == name)
        return out.push_back(f_dir_exists);
    file fi(name, (dir ? fs[dir].full_path + '/' : "/") + name, dir, 0, true, p.h);
    int s = fs.size(); fs[dir].sub.push_back(s); fs.push_back(fi);
}

void find(const string& mp, const params& p, vector<string>& out) {
    int dir; string name;
    if (!valid_path(mp, dir, name)) return out.push_back(path_not_found);
    vector<int> res;
    visit_fs(dir, res, name, p.r, p.h);
    if (res.empty()) return out.push_back(file_not_found);
    sort(res.begin(), res.end(), cmp_p);
    for (int i=0, t=res.size(); i<t; ++i) out.push_back(info(fs[res[i]]));
}

void ls(const string& mp, const params& p, vector<string>& out) {
    int dir; string name;
    if (!valid_path(mp, dir, name, false)) return out.push_back(path_not_found);
    vector<int> res;
    visit_fs(dir, res, "", p.r, p.h, !p.f, !p.d);
    if (res.empty()) return out.push_back(empty_l);
    sort(res.begin(), res.end(), p.s ? cmp_s : (p.S ? cmp_S : cmp_p));
    for (int i=0, t=res.size(); i<t; ++i) out.push_back(info(fs[res[i]]));
}

void run_command(const string& cmd, vector<string>& out) {
    if (cmd.empty()) return;
    istringstream ss(cmd); string s; ss >> s;
    if (s != "cd" && s != "touch" && s!= "mkdir" && s != "find" && s != "ls" && s != "pwd" && s != "exit" && s != "grep") {
        out.push_back(no_such_command);
    } else {
        string mp; params p; ULL v; string s1; ss >> s1;
        while (!s1.empty()) {
            if (s1[0] == '-') {
                if (isalpha(s1[1])) {
                    if (s1[1] == 'r') p.r = true;
                    else if (s1[1] == 'h') p.h = true;
                    else if (s1[1] == 'd') p.d = true;
                    else if (s1[1] == 'f') p.f = true;
                    else if (s1[1] == 's') p.s = true;
                    else if (s1[1] == 'S') p.S = true;
                } else if (istringstream(s1.substr(1)) >> v) p.size = v;
                else return out.push_back(bad_usage);
            } else if (!mp.empty()) return out.push_back(bad_usage);
            else mp = s1;
            s1.clear(); ss >> s1;
        }
        if (s == "cd") mp.empty() ? out.push_back(bad_usage) : cd(mp, out);
        else if (s == "touch") mp.empty() ? out.push_back(bad_usage) : touch(mp, p, out);
        else if (s == "mkdir") mp.empty() ? out.push_back(bad_usage) : mkdir(mp, p, out);
        else if (s == "find") mp.empty() ? out.push_back(bad_usage) : find(mp, p, out);
        else if (s == "ls") ls(mp, p, out);
        else if (s == "pwd") out.push_back(mp.empty() ? fs[curr_dir].full_path : bad_usage);
        else if (s == "grep" || (s == "exit" && !mp.empty())) out.push_back(bad_usage);
        else new_session();
    }
}

void run_cmdline(const string& line) {
    vector<string> cmds, out;
    for (int i=0, t=line.size(), s=0, q=0; i<=t; ++i) if (i==t || (line[i] == '|' && !q)) {
        cmds.push_back(string(line.begin()+s, line.begin()+i)); s = i+1;
    } else if (line[i] == '"') q ^= 1;
    if (cmds.empty()) return;
    run_command(cmds[0], out);
    for (int i=1, t=cmds.size(); i<t; ++i) {
        istringstream ss(cmds[i]); string s, p; ss >> s; getline(ss, p); trim(p);
        if (s != "grep" || p.size() < 2 || p[0] != '"' || p.back() != '"') {
            cout << bad_usage << endl;
            return;
        }
        if (out.empty() || p.size()==2) continue;
        vector<string> filtered; s = p.substr(1, p.size()-2);
        for (int j=0, k=out.size(); j<k; ++j) if (out[j].find(s) != string::npos) filtered.push_back(out[j]);
        out.swap(filtered);
    }
    for (int i=0, t=out.size(); i<t; ++i) cout << out[i] << endl;
}

int main() {
    string line; new_session();
    while (getline(cin, line)) run_cmdline(line);
    return 0;
}
相关推荐
Tisfy8 天前
LeetCode 2553.分割数组中数字的数位:模拟(maybe+翻转)——java也O(1)
java·数学·算法·leetcode·题解·模拟·取模
Estella-Ealine9 天前
5.10 周赛vp 2026 ICPC Gran Premio de Mexico 1ra Fecha E题
icpc·2026
所以遗憾是什么呢?10 天前
【题解】Codeforces Round 1097 (Div. 2, Based on Zhili Cup 2026) (致理杯) ABCDEF
数据结构·算法·acm·codeforces·icpc·ccpc·xcpc
E等于MC平方15 天前
AI 辅助物理课堂实验
人工智能·ai·大模型·模拟·物理·实验
Tisfy16 天前
LeetCode 0796.旋转字符串:暴力模拟
算法·leetcode·题解·模拟·字符串匹配
漂流瓶jz17 天前
UVA-1152 和为0的4个值 题解答案代码 算法竞赛入门经典第二版
数据结构·算法·二分查找·题解·aoapc·算法竞赛入门经典·uva
AKDreamer_HeXY18 天前
QOJ 12255 - 36 Puzzle 题解
数据结构·c++·数学·算法·icpc·qoj
Qres82118 天前
Rabrg/artificial-life test
python·模拟
Tisfy19 天前
LeetCode 0396.旋转函数:求diff
算法·leetcode·题解·模拟·增量法
Tisfy1 个月前
LeetCode 2833.距离原点最远的点:计数
算法·leetcode·字符串·题解·模拟·计数