[lv5] ft_containers(6/6) map(erase)
[(5/6) map(insert)にもどる] https://note.com/syamashi/n/n957bb9ca7719
[(7/6) setにすすむ] https://note.com/syamashi/n/n95838c9d5266
1. map eraseが呼び出せるまで
/containers/map.hpp
//// Modifiers
size_type erase(const key_type& key) { return _M_t.erase(key); }
erase(key)だけ呼び出せるようにします。
戻り値は、成功したら1。失敗(もともと存在しなかった)は0。
2. _Rb_tree eraseが動くまで
erase(key)はこんな感じ
/utils/rb_tree.hpp
public:
// utility function that deletes the node with given value
size_type erase(const key_type& key) {
if (_S_root() == NULL)
// Tree is empty
return 0;
iterator v = lower_bound(key);
if (v == end() || _M_key_compare(key, _S_key(v))) return 0;
deleteNode(v.get_link());
// headerの更新
if (size()) {
_M_root()->_M_parent = _M_header;
_M_header->_M_left = _S_mostleft();
_M_header->_M_right = _S_mostright();
} else {
_M_root() = NULL;
_M_header->_M_left = _M_header;
_M_header->_M_right = _M_header;
}
return 1;
}
Q. headerの更新
A. headerの左右を毎回取り直す実装は、効率度外視の怠慢です。
deleteNodeのアルゴリズム部分は、神サイトを参考に構築します。
https://www.geeksforgeeks.org/red-black-tree-set-3-delete-2/?ref=lbp
これを10回見たほうがいい。
https://www.youtube.com/watch?v=DHDKhWoQcNc
/utils/rb_tree.hpp
// delete
// find node that do not have a left child
// in the subtree of the given node
// 今いる場所から、左の突き当りノード
_Link_type successor(_Link_type x) {
_Link_type temp = x;
while (temp->_M_left != NULL) temp = temp->_M_left;
return temp;
}
// find node that replaces a deleted node in BST
_Link_type BSTreplace(_Link_type x) {
// when node have 2 children
// 子供が2人いるなら、RLLLLL
if (x->_M_left != NULL and x->_M_right != NULL)
return successor(x->_M_right);
// when leaf
if (x->_M_left == NULL and x->_M_right == NULL) return NULL;
// when single child
// 子供が1人なら左から返す。
if (x->_M_left != NULL)
return x->_M_left;
else
return x->_M_right;
}
// deletes the given node
void deleteNode(_Link_type v) {
_Link_type u = BSTreplace(v);
// True when u and v are both black
bool uvBlack =
((u == NULL or u->_M_color == _S_black) and (v->_M_color == _S_black));
_Link_type parent = v->_M_parent;
if (u == NULL) { // vが葉
// u is NULL therefore v is leaf
if (v == _S_root()) {
// v is root, making root null
_M_root() = _M_header;
} else {
if (uvBlack) {
// u and v both black
// v is leaf, fix double black at v
/*
B
/
v:B
*/
fixDoubleBlack(v);
} else {
// v is red
/*
B
/ \
v:R (R)
*/
if (v->sibling() != NULL)
// sibling is not null, make it red"
v->sibling()->_M_color = _S_red;
}
// delete v from the tree
if (v->isOnLeft()) {
parent->_M_left = NULL;
} else {
parent->_M_right = NULL;
}
}
_M_put_node(v);
return;
}
if (v->_M_left == NULL or v->_M_right == NULL) {
// v has 1 child
if (v == _S_root()) {
// v is root, assign the value of u to v, and delete u
/*
v
/
u
u
/ \
NU NU
*/
_M_root() = u;
v->swapNode(u);
u->_M_left = u->_M_right = NULL;
_M_put_node(v);
} else {
// Detach v from tree and move u up
/*
B
/
v:R
/
u:(B)
*/
if (v->isOnLeft()) {
parent->_M_left = u;
} else {
parent->_M_right = u;
}
_M_put_node(v);
u->_M_parent = parent;
if (uvBlack) {
// u and v both black, fix double black at u
fixDoubleBlack(u);
} else {
// u or v red, color u black
u->_M_color = _S_black;
}
}
return;
}
// v has 2 children, swap values with successor and recurse
/*
before del(B10)
B10:v
/ \
R7 R22
/ \ / \
B6 B8 B13:u B26
/
R2
after
B13
/ \
R7 B22
/ \ \
B6 B8 R26
/
R2
*/
if (v == _S_root()) _M_root() = u;
v->swapNode(u);
deleteNode(v);
}
void fixDoubleBlack(_Link_type x) {
if (x == _S_root())
// Reached root
return;
_Link_type sibling = x->sibling();
_Link_type parent = x->_M_parent;
if (sibling == NULL) {
// No sibiling, double black pushed up
fixDoubleBlack(parent);
} else {
if (sibling->_M_color == _S_red) {
// Sibling red
/*
before
B
/ \
x:B R
after
(R)
/ \
x:B (B)
*/
parent->_M_color = _S_red;
sibling->_M_color = _S_black;
if (sibling->isOnLeft()) {
// left case
rightRotate(parent);
} else {
// right case
/*
(B)
/
(R)
/
x:B
*/
leftRotate(parent); // 右が持ち上がる
}
fixDoubleBlack(x);
} else {
// Sibling black
if (sibling->hasRedChild()) {
// 赤い子供がいるならおしまい
// vを削除しても、どの葉からも黒ノードが同じ数になる。
// at least 1 red children
if (sibling->_M_left != NULL and
sibling->_M_left->_M_color == _S_red) { // 左が赤
if (sibling->isOnLeft()) {
// left left
sibling->_M_left->_M_color = sibling->_M_color;
sibling->_M_color = parent->_M_color;
rightRotate(parent);
} else {
// right left
sibling->_M_left->_M_color = parent->_M_color;
rightRotate(sibling);
leftRotate(parent);
}
} else { // 右が赤
if (sibling->isOnLeft()) {
// left right
/*
before
R
/ \
x:B B
/ /
R R
xをdelすると、x下の葉がB0, それ以外がB1になってしまう
changeColor
R
/ \
x:B (R)
/
(B)
leftRotate
(R)
/ \
R B
/
x:B
changeColor
(R)
/ \
parent:(B) B
/
x:B
// xを除いた場合どの葉もB1になった。
*/
sibling->_M_right->_M_color = parent->_M_color;
leftRotate(sibling);
rightRotate(parent);
} else {
// right right
sibling->_M_right->_M_color = sibling->_M_color;
sibling->_M_color = parent->_M_color;
leftRotate(parent);
}
}
parent->_M_color = _S_black;
} else {
// 2 black children
/*
R
/ \
x:B B
/ \
B B
xがなくなるとx側がB1に。右側がB2になってしまう
(B)
/ \
x:B (R)
/ \
B B
*/
sibling->_M_color = _S_red;
if (parent->_M_color ==
_S_black) // parent以下全体の黒が1個減ってしまうので、さらにparentで調整
fixDoubleBlack(parent);
else
parent->_M_color = _S_black;
}
}
}
}
・swapNode
/utils/rb_tree.hpp
// _Rb_tree_node
void swapNode(_Link_type& x) {
// xのまわり
_Link_type xp = x->_M_parent;
_Link_type xr = x->_M_right;
_Link_type xl = x->_M_left;
_Link_type tp = _M_parent;
_Link_type tr = _M_right;
_Link_type tl = _M_left;
if (xp->_M_left == x)
xp->_M_left = this;
else if (xp->_M_right == x)
xp->_M_right = this;
if (xl) xl->_M_parent = this;
if (xr) xr->_M_parent = this;
// 自分のまわり
if (isOnLeft())
tp->_M_left = x;
else
tp->_M_right = x;
if (tl) tl->_M_parent = x;
if (tr) tr->_M_parent = x;
std::swap(_M_color, x->_M_color);
std::swap(_M_parent, x->_M_parent);
std::swap(_M_right, x->_M_right);
std::swap(_M_left, x->_M_left);
}
};
Q. swapNode?なんで?
A. 神サイトだと、swapvalue()箇所。
本当は*M_value_type だけ交換したいですが、期待通りのeraseができなくなりました。絶対動くはずだと思うんですが。
どうにも解消しないので、逆転の発想で、valueを動かさずNodeを全部取り換える実装にしています。
処理数が増えて、かなり怠慢です。
*M_value_typeはポインタなのでswapできると思います。
_M_value_typeの第一引数がconstなので、ポインタの中身はswapできません。
Q. eraseのアルゴリズム
https://www.youtube.com/watch?v=DHDKhWoQcNc
A. ざっくりと
1. 赤を消すほうが楽。
2. 消した後、親戚との関係が「黒黒」になってたら回転対象
3. 黒を消すと「どの葉からも、根までに通る黒の個数が一緒」ルールに合わせるため、全体を組み替える。
4. 改善するための回転方法が3パターンある。
必要なmap実装のアイテムがそろいました。後はスムーズに実装できます。コメントできる関数のみ補足しておきます。
3. map その他関数実装
cppreference
https://en.cppreference.com/w/cpp/container/map
Member functions
・constructer
・destructer
・operator=
/utils/rb_tree.hpp
_Rb_tree& operator=(const _Rb_tree& src) {
if (this == &src) return *this;
_M_erase(_S_root());
// headerは上書き再利用
_M_reset();
_M_key_compare = src._M_key_compare;
_M_node_alloc = src._M_node_alloc;
// root以下クローン
if (src._M_node_count) {
_M_root() = _M_copy(src._M_header->_M_parent, _M_header);
_M_header->_M_left = _S_mostleft();
_M_header->_M_right = _S_mostright();
}
return *this;
}
Q. _M_copy()
A. root以下の要素をまるまるコピーするヘルパーです。
_M_copy(_Const_Link_type x, _Link_type p); // {origin, parent}
head
|
3:root
/ \
1 5
/ \ / \
0 2 4 7
/ \
6 8
\
9
1(x:3 p:header)
2(x:5 p:3)
3(x:7 p:5)
4(x:8 p:7)
5(x:9 p:8)
6(x:2 p:1) // 3->leftの再帰
処理する順序は、
1. rootから探索。
2. 右にすすめるなら右に進んでいく。
3. 左に進めるなら1個左に進んで、2.の処理。
/utils/rb_tree.hpp
_Link_type _M_copy(_Const_Link_type x, _Link_type p) { // {origin, parent}
_Link_type top = _M_clone_node(x);
top->_M_parent = p;
try {
if (x->_M_right) top->_M_right = _M_copy(x->_M_right, top);
p = top;
x = x->_M_left; // 左に進んで次の探索
while (x != 0) {
_Link_type y = _M_clone_node(x);
p->_M_left = y;
y->_M_parent = p;
if (x->_M_right) y->_M_right = _M_copy(x->_M_right, y);
p = y;
x = x->_M_left;
}
} catch (...) {
_M_erase(top);
throw; // throw_exception_again;
}
return top;
}
・get_allocator
Element access
・at
/containers/map.hpp
mapped_type& at(const key_type& k) {
iterator i = lower_bound(k);
if (i == end() || key_comp()(k, (*i).first))
std::__throw_out_of_range("map::at");
return (*i).second;
}
Q. std::__throw_out_of_range
A. atは例外を投げます。operator[]は投げません。
・operator[]
/containers/map.hpp
mapped_type& operator[](const key_type& k) {
iterator i = insert(value_type(k, mapped_type())).first;
return i->second;
}
Q. insert
A. mapの忘れがちで、とても大事な仕様です。
M[1]が存在していない状態で、(M.count(1) == 0のとき)
M[1];
を参照すると、
M[1] = 0;
が自動生成されます。
_M_node_countも+1されます。
Iterators
・begin / end / rbegin / rend
Capacity
・empty
・size
・max_size
max_sizeですが、定義通りに作っても環境差異が出ます。
定義パターン1
std::numeric_limits<size_type>::max() / sizeof(value_type);
定義パターン2
alloc.max_size() をかえす
実装パターン3(これを採用)
ワカモレにベタ合わせ。
参考値
わかもれstd::max_size
vector<char> : max() / 2
vector<int> : max() / 4
map<int,int> : max() / 40
map<int,char> : max() / 40
map<char,int> : max() / 40
map<char,char>: max() / 32
set<int> : max() / 32
set<char> : max() / 32
std::numeric_limits<size_type>::max()
/utils/rb_tree.hpp
size_type max_size() const {
size_t div = sizeof(_Link_type) * 4 + sizeof(value_type);
div = (div / 8) * 8;
return std::numeric_limits<size_type>::max() / div ;
// return _M_node_alloc.max_size();
}
Modifiers
・clear
・insert
・erase
・swap
/utils/rb_tree.hpp
void swap(_Rb_tree& __t) {
// どちらかが size==0 の場合、移す or reset()する
if (_M_root() == 0) {
if (__t._M_root() != 0) _M_move_data(__t);
} else if (__t._M_root() == 0)
__t._M_move_data(*this);
else { // どっちも要素がある場合
std::swap(_M_root(), __t._M_root());
std::swap(_M_header->_M_left, __t._M_header->_M_left);
std::swap(_M_header->_M_right, __t._M_header->_M_right);
_M_root()->_M_parent = _M_end();
__t._M_root()->_M_parent = __t._M_end();
std::swap(_M_node_count, __t._M_node_count);
}
// No need to swap header's color as it does not change.
std::swap(_M_key_compare, __t._M_key_compare);
std::swap(_M_node_alloc, __t._M_node_alloc);
}
Q. swapするのは、要素全部?
A. いいえ。headerだけです。
Lookup
・count
Q. 戻り値は0/1
A. keyの存在有無だけを返します。よく勘違いしがちですが、valを返すわけじゃないです。
M[1] = 100のとき、
M.count(1) == 1
です。
M.count(1) == 100
ではありません。
・find
・equal_range
ft::pair<iterator, iterator> equal_range(const _Key& __k) {
_Link_type __x = _S_root();
_Link_type __y = _M_end();
while (__x != 0) {
if (_M_key_compare(_S_key(__x), __k))
__x = __x->_M_right;
else if (_M_key_compare(__k, _S_key(__x)))
__y = __x, __x = __x->_M_left;
else { // __x が keyに合致。 __yは__x->_M_parent
_Link_type __xu(__x);
_Link_type __yu(__y);
__y = __x,
__x = __x->_M_left; // __yはkeyに一致。 __xはkeyより小さいかNULL
__xu = __xu->_M_right; // __xuはkeyより大きいかNULL
/*
__x : key->left
__y : keyに一致
__xu : key->right
__yu : keyに一致
_M_lower_bound(__x, __y, __k) :
__x==NULLなら__y
__xのできるだけ右を返す == keyの前
_M_upper_bound(__xu, __yu, __k) :
__xu==NULLなら__yu。
__xuのできるだけ左を返す == keyの次
つまり、
p.first = M.lower_bound(key);
p.second = M.upper_bound(key);
を効率化してるだけ
*/
return ft::pair<iterator, iterator>(_M_lower_bound(__x, __y, __k),
_M_upper_bound(__xu, __yu, __k));
}
}
// 末端まで来た
return ft::pair<iterator, iterator>(iterator(__y), iterator(__y));
}
Q. 簡単にいうと
A. これで十分です。
第一引数に、lower_bound(__k)
第二引数に、upper_bound(__k)
を返すだけ。
でも、これはrootから葉までを2 回探索する怠慢。
1 回の探索で返せる実装にしたのが上記。
・lower_bound
・upper_bound
Observers
・key_comp
・value_comp
Non-member functions
・map終了時点でセーブ。
テストケース追加してます。
/testfiles/test_map.cpp
/testfiles/main.cpp
/testfiles/tester.hpp
https://github.com/syamashi/ft_container/tree/55ac028b01824f04503c6e821ac2b396cdc0c3a3/newgame
[(7/6) setにすすむ] https://note.com/syamashi/n/n95838c9d5266