零、前言
Project0 主要就是环境搭建,项目内容还是很简单的。
这里只是简略的记录一下过程,不会太详细,越来越忙了啊……
一、环境搭建
官方让用 Ubuntu22.04,这里我是 WSL + Ubuntu20.04,暂时能用出问题再说,
用WSL主要是不想在VMware上装CLion或者VScode了
VSCode 连接 WSL 装了4个插件
然后 Ubuntu 上要装的各种工具我也记不太清了,一般遇到的时候 apt intall 也不晚
需要注意的是 CodeLLDB 这个插件挺逆天的,必须得通过VSIX装(至少我本地 和 WSL上都是)
一般就是你在VSCode上装完之后,过一会会报错,点击在URL上下载,然后自己想办法通过terminal 命令 或者 VSCode 在扩展那里点击 Install from VSIX 。
然后就是 把 bustub 那个仓库部署到本地了
在 CMU15445 那个 github 主页 跟着来就行 https://github.com/cmu-db/bustub
二、Project0 流程
字典树还是很简单的,关于字典树我也有博客:
[Trie树/字典树的原理及实现C/C++]_trie字典树原理-CSDN博客
2.1 TASK1
TASK1 就是字典树基操,字典树都大同小异,看一下它的 TrieNode 定义然后直接上手写就行
他只不过把指针换成了智能指针,这里也只涉及了 shared_ptr 和 unique_ptr,二者分别是引用计数法 和 禁止拷贝,也比较简单,我之前也有写博客:[让内存无处可逃:智能指针C++11]_接口用智能指针-CSDN博客
代码逻辑很简单,也不再说了,码风 是我个人码风 被提交时的格式检查 恶心之后改成这样的,可读性应该还能看吧……
因为比较简单,我也没写什么注释
Get
template <class T>
auto Trie::Get(std::string_view key) const -> const T * {
auto cur = root_;
for (char ch : key) {
if (cur == nullptr || cur->children_.find(ch) == cur->children_.end()) {
return nullptr;
}
cur = cur->children_.at(ch);
}
const auto *res = dynamic_cast<const TrieNodeWithValue<T> *>(cur.get());
if (res != nullptr) {
return res->value_.get();
}
return nullptr;
// You should walk through the trie to find the node corresponding to the key. If the node doesn't exist, return
// nullptr. After you find the node, you should use `dynamic_cast` to cast it to `const TrieNodeWithValue<T> *`. If
// dynamic_cast returns `nullptr`, it means the type of the value is mismatched, and you should return nullptr.
// Otherwise, return the value.
}
Put
template <class T>
auto Trie::Put(std::string_view key, T value) const -> Trie {
// 如果在根插入,我们就要特判 TrieNodeWithValue 了
if (key.empty()) {
std::shared_ptr<T> node_value = std::make_shared<T>(std::move(value));
// move: 因为 提示了 T 可能为 unique_ptr 类型
std::shared_ptr<TrieNodeWithValue<T>> new_root = nullptr;
if (root_->children_.empty()) {
new_root = std::make_shared<TrieNodeWithValue<T>>(node_value);
} else {
new_root = std::make_shared<TrieNodeWithValue<T>>(root_->children_, node_value);
}
return Trie(std::move(new_root));
}
std::shared_ptr<TrieNode> new_root = nullptr;
if (root_ == nullptr) {
new_root = std::make_shared<TrieNode>();
} else {
new_root = root_->Clone();
}
std::shared_ptr<TrieNode> cur = new_root;
for (size_t i = 0; i + 1 < key.size(); ++i) {
char c = key.at(i);
auto it = cur->children_.find(c);
if (it == cur->children_.end()) {
auto ptr = std::make_shared<TrieNode>();
cur->children_.insert({c, ptr});
cur = ptr;
} else {
std::shared_ptr<TrieNode> ptr = it->second->Clone();
it->second = ptr;
cur = ptr;
}
}
char c = key.back();
auto it = cur->children_.find(c);
if (it == cur->children_.end()) {
std::shared_ptr<T> val_p = std::make_shared<T>(std::move(value));
cur->children_.insert({c, std::make_shared<TrieNodeWithValue<T>>(val_p)});
} else {
std::shared_ptr<T> val_p = std::make_shared<T>(std::move(value));
it->second = std::make_shared<TrieNodeWithValue<T>>(it->second->children_, val_p);
}
return Trie(new_root);
}
Remove
auto Trie::Remove(std::string_view key) const -> Trie {
if (this->root_ == nullptr) {
return *this;
}
// 同样特判键为空
if (key.empty()) {
// TreeNodeWithValue
if (root_->is_value_node_) {
if (root_->children_.empty()) {
return Trie(nullptr);
}
std::shared_ptr<TrieNode> new_root = std::make_shared<TrieNode>(root_->children_);
return Trie(new_root);
}
// 否则直接返回
return *this;
}
std::shared_ptr<TrieNode> new_root = root_->Clone();
auto dfs = [&](auto &&self, const std::shared_ptr<TrieNode> &cur, size_t i) -> bool {
char c = key.at(i);
auto it = cur->children_.find(c);
if (it == cur->children_.end()) {
return false;
}
if (i + 1 == key.size()) {
if (!it->second->is_value_node_) {
return false;
}
if (it->second->children_.empty()) {
cur->children_.erase(it);
} else {
it->second = std::make_shared<TrieNode>(it->second->children_);
}
return true;
}
std::shared_ptr<TrieNode> ptr = it->second->Clone();
if (!self(self, ptr, i + 1)) {
return false;
}
if (ptr->children_.empty() && !ptr->is_value_node_) {
cur->children_.erase(it);
} else {
it->second = ptr;
}
return true;
};
if (dfs(dfs, new_root, 0)) {
if (new_root->children_.empty() && !new_root->is_value_node_) {
new_root = nullptr;
}
return Trie(new_root);
}
return *this;
// You should walk through the trie and remove nodes if necessary. If the node doesn't contain a value any more,
// you should convert it to `TrieNode`. If a node doesn't have children any more, you should remove it.
}
2.2 TASK2
TASK2 就更简单了,如果有学过 操作系统的话,无非就是经典的 读者写者操作
项目那里说是 多读者 单写者,那么为了保证线程安全,我们上锁即可
后续我可能会写博客记录下 C++并发实战?
Get
template <class T>
auto TrieStore::Get(std::string_view key) -> std::optional<ValueGuard<T>> {
// Pseudo-code:
// (1) Take the root lock, get the root, and release the root lock. Don't lookup the value in the
// trie while holding the root lock.
// (2) Lookup the value in the trie.
// (3) If the value is found, return a ValueGuard object that holds a reference to the value and the
// root. Otherwise, return std::nullopt.
std::lock_guard<std::mutex> lock(root_lock_);
auto trie = root_;
const T *val = trie.Get<T>(key);
if (val == nullptr) {
return std::nullopt;
}
return ValueGuard<T>(trie, *val);
}
Put
template <class T>
void TrieStore::Put(std::string_view key, T value) {
// You will need to ensure there is only one writer at a time. Think of how you can achieve this.
// The logic should be somehow similar to `TrieStore::Get`.
std::lock_guard<std::mutex> lock(write_lock_);
auto new_trie = root_.Put<T>(key, std::move(value));
std::lock_guard<std::mutex> root_lock(root_lock_);
root_ = new_trie;
}
Remove
void TrieStore::Remove(std::string_view key) {
// You will need to ensure there is only one writer at a time. Think of how you can achieve this.
// The logic should be somehow similar to `TrieStore::Get`.
std::lock_guard<std::mutex> lock(write_lock_);
auto new_trie = root_.Remove(key);
std::lock_guard<std::mutex> root_lock(root_lock_);
root_ = new_trie;
}
2.3 TASK3
emm
前面安装了 LLDB,所以这里直接调试去看即可
LLDB 那个 json 文件 只需要写一下路径就行
#include "primer/trie.h"
// TODO(student): Equinox
const uint32_t CASE_1_YOUR_ANSWER = 10;
const uint32_t CASE_2_YOUR_ANSWER = 1;
const uint32_t CASE_3_YOUR_ANSWER = 25;
2.4 TAKS4
这个就是让你写个大小写转换函数,也没什么说的
auto Compute(const std::string &val) const -> std::string {
// TODO(student): implement upper / lower.
std::string res;
switch (expr_type_) {
case StringExpressionType::Lower:
for (auto ch : val) {
res.push_back(std::tolower(ch));
}
break;
case StringExpressionType::Upper:
for (auto ch : val) {
res.push_back(std::toupper(ch));
}
break;
default:
break;
}
return res;
}
// NOLINTNEXTLINE
auto Planner::GetFuncCallFromFactory(const std::string &func_name, std::vector<AbstractExpressionRef> args)
-> AbstractExpressionRef {
// 1. check if the parsed function name is "lower" or "upper".
// 2. verify the number of args (should be 1), refer to the test cases for when you should throw an `Exception`.
// 3. return a `StringExpression` std::shared_ptr.
if ((func_name == "lower" || func_name == "upper") && args.size() == 1) {
return static_cast<std::shared_ptr<StringExpression>>(std::make_shared<StringExpression>(
args[0], func_name == "lower" ? StringExpressionType::Lower : StringExpressionType::Upper));
}
throw Exception(fmt::format("func call {} not supported in planner yet", func_name));
}
2.5 本地测试
TASK1
在 build 文件夹下
# make -j12 trie_test
# ./test/trie_test
代码没问题的话就是这样子
TASK2
# make -j12 trie_store_test
# ./test/trie_store_test
TASK3 和 TASK4 我这里本地测会有点问题但是提交没问题,很怪,就不放了。
2.6 提交
实验平台:https://www.gradescope.com/
2023Fall的码:KK5DVJ
自行创建账号
然后就是最恶心的部分了,提交前的格式检查:
# sudo apt-get install clang-format
# apt-get install clang-tidy
然后在build文件夹下:
# make format
# make check-lint
# make check-clang-tidy-p0
然后回到主目录,即 build 的上一级目录,这里调用一下 py 脚本
# python3 gradescope_sign.py
第一次调用的话就是各种signature,无脑确认就行
2.7 个人提交时遇到的问题
- 我遇到的第一个问题: make format 会告诉我 一个 什么 全是大写的文件不存在,我忘了具体是什么了,最后是重新在 build 文件夹下执行最初的初始化操作后就没事了(就是 github主页那里写的那个)
- 第二个问题:码风问题。执行 make check-clang-tidy-p0 后告诉我仨cpp文件 failed,其实是格式不对,提交上去全pass了还是0分,最后发现 平台上
这个测试点会有 具体的格式错误的信息,照着改(比如文件结尾必须有一行空行啊,所有语句块就算一行也得用 花括号啊,驼峰命名让你加下划线啊之类的)
这两个问题耗我时间最长,别的问题我都忘了哈铪,可以自行搜索。
提交测完是这个样子: