CUM-15445 fall2023 project0

前言

15445通关记录贴-project0

Task #1 - Copy-On-Write Trie

先写put,再写get和remove
这个任务的代码是用test/trie_test.cpp进行测试的,目前所有测试用例都可以通过

put方法

我是写的非递归版本。首先要单独处理key==""这种情况。key不为空时,遍历key的每个字符,当key[i]在curr->children的时候,就对curr->children[key[i]]进行复制,不在的时候就新建一个节点。然后将复制/新建的节点连接到curr->children[key[i]]。循环遍历直到i=key.size()-1,此时需要单独处理最后一个字符,因为最后一个字符对应的节点必须是带值的节点。如果最后一个字符所对应的节点已经存在,那么根据这个节点的children_和函数参数value创建一个值节点,否则只根据value创建值节点而不需要children_,然后将新节点连接到curr上

auto Trie::Put(std::string_view key, T value) const -> Trie {
  // 由于value可能是不可拷贝的,所以需要使用move函数转移资源的控制权
  auto vptr = std::make_shared<T>(std::move(value));
  // 先处理key==""
  if(key==""){
    if(root_) return Trie(std::make_shared<TrieNodeWithValue<T>>(root_->children_,vptr));
    else return Trie(std::make_shared<TrieNodeWithValue<T>>(vptr));;
  }
  
  // 根为空时先创建一个根
  std::shared_ptr<TrieNode> root;
  if(root_) root = std::move(root_->Clone());
  else root = std::make_shared<TrieNode>();

  // 遍历的过程需要不断复制遍历到的节点,如果一个节点的孩子中没有key[i],那就创建新的节点
  auto curr = root;
  for(size_t i=0; i<key.size(); i++){
    auto it = curr->children_.find(key[i]);
    auto p = std::make_shared<TrieNode>();
    if(i != key.size()-1){// 非最后一个字符
      if(it != curr->children_.end()) //存在则复制
        p = it -> second -> Clone();
    }
    else{ // 对于最后一个字符,要插入一个带值的节点,无论这个节点是否存在都要创建一个新的值节点
      if(it != curr->children_.end())
        p = std::make_shared<TrieNodeWithValue<T>>(it->second->children_,vptr);
      else
        p = std::make_shared<TrieNodeWithValue<T>>(vptr);
    }
    curr->children_[key[i]] = p;     
    curr = p; 
  }

  return Trie(root);
}

get方法

get方法很简单。根据key[i]遍历trie,如果某个节点不存在则直接返回null,如果一直遍历到最后一个字符都有节点存在,那么要判断最后一个节点是否是值节点,如果不是则返回null,如果是则返回节点的value。这里涉及到指针的动态转换,有两种写法。一种是在智能指针之间进行转换,一种是在裸指针之间进行转换

auto Trie::Get(std::string_view key) const -> const T * {
  // 根为空
  if(root_==nullptr)
    return nullptr;

  // 根非空
  auto curr = root_;
  size_t i =0;
  while(i < key.size()){
    auto it = curr->children_.find(key[i]);
    if(it != curr->children_.end())
      curr = it->second;
    else
      return nullptr;
    i++;
  }

  // 写法1
  // std::shared_ptr<const TrieNodeWithValue<T>> p = std::dynamic_pointer_cast<const TrieNodeWithValue<T> >(curr);
  // if(p)
  //   return p->value_.get();
  // return nullptr;

  // 写法2
  const TrieNodeWithValue<T>* node = dynamic_cast<const TrieNodeWithValue<T>*>(curr.get());
  if (node)
    return node->value_.get();
  return nullptr;
}

remove方法

非递归版本,遍历的过程中要用栈保存遍历的路径。之后通过栈一个个删除孩子数为0且不含值的节点

auto Trie::Remove(std::string_view key) const -> Trie {
  // 根为空
  if(root_==nullptr)
    return Trie();

  // 键为空串
  if(key==""){
    auto root = std::make_shared<TrieNode>(root_->children_);
    if (root -> children_.size() == 0)
      return Trie();
    else
      return Trie(root);
  }
  // 正常情况,用栈保存路径以便于删除
  std::stack<std::shared_ptr<TrieNode>> node_stack;
  std::stack<char> char_stack;
  std::shared_ptr<TrieNode> root = std::move(root_->Clone());
  auto curr = root;
  node_stack.push(root);
  // 遍历以定位要删除的节点
  for (std::size_t i = 0; i < key.size(); ++i) {
    auto it = curr->children_.find(key[i]);
    std::shared_ptr<TrieNode> p;
    if(it != curr->children_.end()){
      if(i == key.size() - 1)
        // 最后一个字符对应的节点必须转成TrieNode,以表示他对应的key被删除了,这里就需要创建新节点
        p = std::make_shared<TrieNode>(it -> second -> children_);
      else
        // 非最后一个字符对应的节点仍需遵循写时复制的原则,需要拷贝
        p = it -> second -> Clone();
      curr->children_[key[i]] = p;     
      curr = p;
      node_stack.push(p);
      char_stack.push(key[i]);
    }
    else
      // 某个字符对应的节点不存在,说明key不存在,不需要删除任何节点
      return Trie(root);
  }
  // 只要能走到这里,说明待删除的节点是肯定存在的,后续就是不断删除从这个节点往根节点的路径上的孩子数为0且不含值的节点
  // 从待删除节点开始,不断删除孩子数为0且不含值的节点,直到遇到孩子数不为0或含值的节点则停止
  auto sub = node_stack.top();
  node_stack.pop();
  while(node_stack.size() != 0){
    auto parent = node_stack.top();
    if(sub->children_.size() == 0 && sub->is_value_node_ == false)
      parent->children_.erase(char_stack.top());
    else
      break;
    node_stack.pop();
    char_stack.pop();
    sub = parent;
  }
  // 根可能也是需要删除的,上面删除节点的循环实际上没办法处理根节点,因为根节点没有父节点,所以需要单独处理一下根节点
  if(root->children_.size()==0 && sub->is_value_node_ == false)
    return Trie();
  return Trie(root);
}

Task #2 - Concurrent Key-Value Store

这里需要学一下C++里面跟锁有关的东西,std::mutex是一种互斥量,针对它实现的锁只有两种std::lock_guard、std::unique_lock。他们都是对互斥量上锁以实现互斥,但是std::lock_guard没法手动解锁,std::unique_lock可以调用unlock()手动解锁,std::unique_lock的粒度更细。

按我的理解,我以为要用std::shared_lock和std::unique::lock来实现读写锁(读与读可以并发,读与写互斥,读与读互斥),结果发现std::shared_lock只能对std::shared_mutex上锁,不能用于std::mutex。

可以看下这篇博客对c++锁的介绍
C++锁的一些介绍

auto TrieStore::Get(std::string_view key) -> std::optional<ValueGuard<T>> {
  std::unique_lock<std::mutex> rlock(root_lock_);
  auto root = root_;
  rlock.unlock();

  auto value = root.Get<T>(key);
  if(value)
    return ValueGuard<T>(root,*value);
  return std::nullopt;
}

template <class T>
void TrieStore::Put(std::string_view key, T value) {
  std::unique_lock<std::mutex> wlock(this->write_lock_);
  const Trie trie = this->root_.Put<T>(key, std::move(value));
  std::unique_lock<std::mutex> rlock(root_lock_);
  root_ = trie;
  rlock.unlock();
  wlock.unlock();
}

void TrieStore::Remove(std::string_view key) {
  std::unique_lock<std::mutex> wlock(this->write_lock_);
  const Trie trie = this->root_.Remove(key);
  std::unique_lock<std::mutex> rlock(root_lock_);
  root_ = trie;
  rlock.unlock();
  wlock.unlock();
}

Task #3 - Debugging

第一问:调试可以看到root有9个孩子
在这里插入图片描述
但是这里gdb没办法显示更深层的节点,比如我想display trie.root_->children_.at(‘9’) 这样查看root的第九个子节点,会报错:
trie.root_->children_.at(‘9’) = <error: Cannot evaluate function – may be inlined>
也就是说它没法查看更深层的节点了,所以直接上输出语句吧,我想这里应该是有办法用gdb查看深层节点的,但是我懒得去查资料了

第二问和第三问:
在这里插入图片描述
答案分别是1和25

Task #4 - SQL String Functions

string_expression.h在src/include/execution/expressions下
plan_func_call.cpp在src/planner下
单论字符串的大小写转换的代码当然是很简单的,但是说实话我没看懂这里的的函数调用关系以及这里的这些类,感觉很复杂,不知道这里的代码该咋写,因此直接参考了别人的代码(太菜了):
参考博文

  • 11
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值