以下操作在题目文档中均有提及,这里进行简要整理。
首先需要从远程仓库克隆项目文件,由于该仓库会随每年的课程一起更新,所以需要根据课程时间指定相应分支。
git clone --branch v20221128-2022fall https://github.com/cmu-db/bustub.git
下载下来后需要在项目根目录下执行 build_support/packages.sh
安装BusTub需要的包,具体执行哪个脚本因操作系统而异。
随后在项目根目录执行以下命令,通过cmake构建项目,这里的 -DCMAKE_BUILD_TYPE=Debug
表示在调试模式下构建项目,即在没有优化的情况下,使用带有调试符号的方式构建库或可执行文件。
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Debug ..
make
本项目通过GoogleTest完成代码测试及评分,题目文件为 src/include/primer/p0_trie.h
,测试文件为 test/primer/starter_trie_test.cpp
,测试文件中的每一个 TEST
对应一个测试单元。
我们在刚才创建的build目录下,通过以下命令执行测试,这里需要注意的是,需要去掉测试文件的代码中的 DISABLED_
前缀以激活对应测试单元。
make starter_trie_test
./test/starter_trie_test
执行运行命令后控制台会打印测试结果,[ OK ]
即对应单元测试通过,如果有 [ DISABLED ]
则表示对应单元中的 DISABLED_
前缀没有删除。
atreus@MacBook-Pro % ./test/starter_trie_test
Running main() from gmock_main.cc
[==========] Running 5 tests from 2 test suites.
[----------] Global test environment set-up.
[----------] 3 tests from StarterTest
[ RUN ] StarterTest.TrieNodeInsertTest
[ OK ] StarterTest.TrieNodeInsertTest (0 ms)
[ RUN ] StarterTest.TrieNodeRemoveTest
[ OK ] StarterTest.TrieNodeRemoveTest (0 ms)
[ RUN ] StarterTest.TrieInsertTest
[ OK ] StarterTest.TrieInsertTest (0 ms)
[----------] 3 tests from StarterTest (0 ms total)
[----------] 2 tests from StarterTrieTest
[ RUN ] StarterTrieTest.RemoveTest
[ OK ] StarterTrieTest.RemoveTest (0 ms)
[ RUN ] StarterTrieTest.ConcurrentTest1
[ OK ] StarterTrieTest.ConcurrentTest1 (184 ms)
[----------] 2 tests from StarterTrieTest (184 ms total)
[----------] Global test environment tear-down
[==========] 5 tests from 2 test suites ran. (184 ms total)
[ PASSED ] 5 tests.
atreus@MacBook-Pro %
我们也可以自己指定测试用例:
TEST(StarterTest, MyTest) {
Trie trie;
bool success;
success = trie.Insert<int>("a", 1);
EXPECT_EQ(success, true);
EXPECT_EQ(trie.GetValue<int>("a", &success), 1);
success = trie.Insert<int>("abc", 3);
EXPECT_EQ(success, true);
EXPECT_EQ(trie.GetValue<int>("abc", &success), 3);
success = trie.Insert<int>("ab", 2);
EXPECT_EQ(success, true);
EXPECT_EQ(trie.GetValue<int>("ab", &success), 2);
success = trie.Remove("ab");
EXPECT_EQ(success, true);
trie.GetValue<int>("ab", &success);
EXPECT_EQ(success, false);
success = trie.Insert<int>("ab", 5);
EXPECT_EQ(success, true);
EXPECT_EQ(trie.GetValue<int>("ab", &success), 5);
}
代码最终要提交到在线网站,如果代码风格不遵循Google C++ Style Guide会直接被判零分,因此我们需要对代码进行格式化。
在build目录下执行 make format
通过python脚本自动格式化代码,然后执行 make check-lint
和 make check-clang-tidy-p0
检查格式化结果,控制台将打印代码格式问题,依次进行修改即可。
如果完成格式话要求的话输出大致如下:
atreus@MacBook-Pro % make format
Built target format
atreus@MacBook-Pro % make check-lint
Built target check-lint
atreus@MacBook-Pro % make check-clang-tidy-p0
Enabled checks:
bugprone-argument-comment
bugprone-assert-side-effect
bugprone-bad-signal-to-kill-thread
......
readability-uniqueptr-delete-release
readability-uppercase-literal-suffix
readability-use-anyofallof
Checking: /Users/atreus/CLionProjects/bustub/src/primer/p0_trie.cpp
Built target check-clang-tidy-p0
atreus@MacBook-Pro %
我们通过 zip
命令压缩 p0_trie.h
文件。
这里需要注意的是,压缩文件时文件名中需要包含完整的 src/include/primer/p0_trie.h
路径,因此我们在项目根目录下进行压缩:
atreus@MacBook-Pro % zip project0-submission.zip ./src/include/primer/p0_trie.h # 压缩
updating: src/include/primer/p0_trie.h (deflated 72%)
atreus@MacBook-Pro % unzip -l project0-submission.zip # 检查压缩文件内容
Archive: project0-submission.zip
Length Date Time Name
--------- ---------- ----- ----
13702 05-31-2023 00:00 src/include/primer/p0_trie.h
--------- -------
13702 1 file
atreus@MacBook-Pro %
代码需要提交到以下网址进行评测,提交前需要以CMU进行注册:
https://www.gradescope.com/courses/424375/
如果评测成功结果大致如下:
其实这个关于线上测试有个小技巧,因为线上评测的用例比本地提供的要复杂一些,但是它只会提供给你最终结果,而不是像LeetCode一样给出失败的测试用例。
不过我们可以通过 system("cat /autograder/bustub/test/primer/grading_starter_trie_test.cpp");
从在线测试平台上获取测试文件的源代码,从而查看测试用例。我们只需要在 p0_trie.h
文件的任意一个函数中执行上述语句即可,这里的文件名可以参考在线测试平台的输出以及本地目录结构。
Insert
template<typename T>
auto Insert(const std::string &key, T value) -> bool {
/* 通过unique_lock使用互斥锁 */
std::unique_lock<std::shared_mutex> lock(shared_mutex_);
if (key.empty()) {
return false;
}
auto cur_node = &root_;
for (int i = 0; i < static_cast<int>(key.size()); i++) {
char cur_c = key.at(i); // 本次要插入的字符
if (i == static_cast<int>(key.size()) - 1) {
/* 如果待插入位置已经存在一个节点了,需要判断它是不是值节点,否则直接插入。 */
if ((*cur_node)->HasChild(cur_c)) {
auto next_node = (*cur_node)->GetChildNode(cur_c); // 待插入位置的节点
/* key对应的位置已经存在一个值节点了 */
if ((*next_node)->IsEndNode()) {
return false;
}
/* 用一个TrieNodeWithValue节点替换这个TrieNode节点 */
auto new_node = std::make_unique<TrieNodeWithValue < T >>
(std::move(*(*next_node)), value);
(*cur_node)->RemoveChildNode(cur_c);
(*cur_node)->InsertChildNode(cur_c, std::move(new_node));
} else {
(*cur_node)->InsertChildNode(cur_c, std::make_unique<TrieNodeWithValue < T >>
(cur_c, value));
}
} else {
/* 如果当前字符对应的节点已经存在直接向下遍历,否则需要先插入再访问。 */
if ((*cur_node)->HasChild(cur_c)) {
cur_node = (*cur_node)->GetChildNode(cur_c);
} else {
cur_node = (*cur_node)->InsertChildNode(cur_c, std::make_unique<TrieNode>(cur_c));
}
}
}
return true;
}
Remove
auto Remove(const std::string &key) -> bool {
std::vector<std::unique_ptr<TrieNode> *> records; // 用于记录访问过的节点,便于后续删除
/* 通过unique_lock使用互斥锁 */
std::unique_lock<std::shared_mutex> lock(shared_mutex_);
/* 按照key查找节点 */
records.emplace_back(&root_);
for (int i = 0; i < static_cast<int>(key.size()); i++) {
std::unique_ptr<TrieNode> *next_node = (*records.at(i))->GetChildNode(key.at(i));
/* 满足条件的子节点不存在,查找失败。 */
if (next_node == nullptr) {
return false;
}
/* 将每一个访问过的节点入队 */
records.emplace_back(next_node);
}
/* key对应的节点为一个非值节点,查找失败。 */
if (!(*records.at(static_cast<int>(records.size()) - 1))->IsEndNode()) {
return false;
}
/* 从下到上依次删除节点 */
for (int i = static_cast<int>(records.size()) - 1; i > 0; i--) {
/* 如果待删除的节点有孩子,只需要将is_end_置为false即可,否则需要从父节点中删除当前节点。
* 这里一个更好的做法是将这个TrieNodeWithValue节点转换为TrieNode节点,这样能节省一定内存。 */
if ((*records.at(i))->HasChildren()) {
(*records.at(i))->SetEndNode(false);
} else {
(*records.at(i - 1))->RemoveChildNode(key.at(i - 1));
}
/* 下一个要处理的节点为值节点,停止删除。 */
if ((*(*records.at(i - 1))).IsEndNode()) {
break;
}
}
return true;
}
GetValue
template<typename T>
auto GetValue(const std::string &key, bool *success) -> T {
/* 通过shared_lock使用共享锁 */
std::shared_lock<std::shared_mutex> lock(shared_mutex_);
*success = true;
if (key.empty()) {
*success = false;
return {};
}
/* 按照key查找节点 */
std::unique_ptr<TrieNode> *cur_node = &root_;
for (char c : key) {
cur_node = (*cur_node)->GetChildNode(c);
if (cur_node == nullptr) {
*success = false;
return {};
}
}
/* 查找失败 */
if (!(*cur_node)->IsEndNode()) {
*success = false;
return {};
}
/* 检查范型T和实际存储的数据类型是否一致 */
auto result = dynamic_cast<TrieNodeWithValue <T> *>(&(*(*cur_node)));
if (result == nullptr) {
*success = false;
return {};
}
return result->GetValue();
}
参考:
https://zhuanlan.zhihu.com/p/619220493
https://cloud.tencent.com/developer/article/2286131
https://www.cnblogs.com/suqinglee/p/16684055.html