没有 Boost 的现代 C++ 中的 bimap 实现
bimap implementation in modern C++ without Boost
这个问题以前有人问过here我承认,但现在已经是 4 年前了,所以我敢要求更新:
我需要一种方法来将 tuple/pair 添加到容器并有效地搜索左右元素。
Boost 具有 bimap
和 multi_index
,它们完全符合我的要求,但我想知道在普通现代 C++-11/14 中推荐的替代方案是什么,以防您不想引入依赖项提升(无论出于何种原因)。
链接中的一个答案表明不需要 s.th。由于 透明比较器 ,不再像双图一样。接受的答案建议将 std::map
s 与 key1
-> key2
和 key2
-> key1
.
相结合的实现
我真的不知道透明比较器是如何帮助我的,我只是好奇是否有一些你应该怎么做以及为什么 - 解决方案。你能提供一些吗hints/links?
我认为这只是很多繁琐的工作。
为了好玩,我开始在这里实现一个起点。
备注:
- 'main' 支持是
std::list<tuple<K,V>>
。这确保 iterator/reference 失效语义尽可能接近 std::map
- 主背景兼作 "left" 视图(仅供外部使用的只读形式)
- "right" 视图非常相似,但使用
std::reference_wrapper<value_type const>
所以我们只索引第一个容器,真的
我只实现了简单的查询(lower_bound
、upper_bound
、equal_range
和 find
)。当然有迭代器和 ranged-for。
您还有一些事情要做(erase
、emplace
、范围插入、initializer_list 构造;有状态 allocator/comparator 支持很粗略(没有构造函数考虑相关参数)但范围分配器已被考虑在内。)
事不宜迟:
#include <algorithm>
#include <tuple>
#include <list>
#include <functional> // for std::reference_wrapper
#include <cassert>
namespace bimap {
namespace detail {
template <typename Cmp, typename V, size_t I> struct CompareByElement : private Cmp {
bool operator()(V const& a, V const& b) const {
using std::get;
return Cmp::operator()(get<I>(a), get<I>(b));
}
bool operator()(V const& a, V const& b) {
using std::get;
return Cmp::operator()(get<I>(a), get<I>(b));
}
};
namespace tags {
struct left_view;
struct right_view;
}
template <typename ViewTag, typename Left, typename Right, typename LeftCmp, typename RightCmp, typename RawAlloc>
struct view_traits;
template <typename Left, typename Right, typename LeftCmp, typename RightCmp, typename RawAlloc>
struct view_traits<tags::left_view, Left, Right, LeftCmp, RightCmp, RawAlloc> {
using value_type = std::tuple<Left, Right>;
using allocator_type = typename RawAlloc::template rebind<value_type>::other;
using base_type = std::list<value_type, allocator_type>;
using comparator = CompareByElement<LeftCmp, value_type, 0>;
};
template <typename Left, typename Right, typename LeftCmp, typename RightCmp, typename RawAlloc>
struct view_traits<tags::right_view, Left, Right, LeftCmp, RightCmp, RawAlloc> {
using value_type = std::tuple<Left, Right>;
using allocator_type = typename RawAlloc::template rebind<std::reference_wrapper<value_type const>>::other;
using base_type = std::list<std::reference_wrapper<value_type const>, allocator_type>;
using comparator = CompareByElement<RightCmp, value_type, 1>;
};
template <typename Left, typename Right, typename LeftCmp, typename RightCmp, typename RawAlloc>
struct bimap_traits {
using left_traits = view_traits<tags::left_view, Left, Right, LeftCmp, RightCmp, RawAlloc>;
using right_traits = view_traits<tags::right_view, Left, Right, LeftCmp, RightCmp, RawAlloc>;
};
template <typename Traits> struct map_adaptor :
private Traits::base_type,
private Traits::comparator // empty base class optimization
{
using value_type = typename Traits::value_type;
using allocator_type = typename Traits::allocator_type;
using base_type = typename Traits::base_type;
using comparator = typename Traits::comparator;
using iterator = typename base_type::iterator;
using const_iterator = typename base_type::const_iterator;
using base_type::cbegin;
using base_type::cend;
using base_type::begin;
using base_type::end;
using base_type::insert;
using base_type::size;
auto lower_bound(value_type const& v) { return std::lower_bound(base_type::begin(), base_type::end(), v, get_comp()); }
auto upper_bound(value_type const& v) { return std::upper_bound(base_type::begin(), base_type::end(), v, get_comp()); }
auto equal_range(value_type const& v) { return std::equal_range(base_type::begin(), base_type::end(), v, get_comp()); }
auto lower_bound(value_type const& v) const { return std::lower_bound(base_type::begin(), base_type::end(), v, get_comp()); }
auto upper_bound(value_type const& v) const { return std::upper_bound(base_type::begin(), base_type::end(), v, get_comp()); }
auto equal_range(value_type const& v) const { return std::equal_range(base_type::begin(), base_type::end(), v, get_comp()); }
auto find(value_type const& v) {
auto er = equal_range(v);
return er.first == er.second? end() : er.first;
}
auto find(value_type const& v) const {
auto er = equal_range(v);
return er.first == er.second? end() : er.first;
}
base_type& base() { return *static_cast<base_type*>(this); }
base_type const & base() const { return *static_cast<base_type const*>(this); }
private:
comparator& get_comp() { return *this; }
comparator const& get_comp() const { return *this; }
};
}
template <typename Left, typename Right,
typename LeftCmp = std::less<Left>, typename RightCmp = std::less<Right>,
typename RawAlloc = std::allocator<void>,
typename Traits = detail::bimap_traits<Left, Right, LeftCmp, RightCmp, RawAlloc>
>
class bimap : private detail::map_adaptor<typename Traits::left_traits> {
public:
using left_type = typename detail::map_adaptor<typename Traits::left_traits>;
using right_type = typename detail::map_adaptor<typename Traits::right_traits>;
using value_type = typename Traits::left_traits::value_type;
using allocator_type = typename Traits::left_traits::allocator_type;
using base_type = left_type;
using const_iterator = typename base_type::const_iterator;
using iterator = const_iterator;
using base_type::cbegin;
using base_type::cend;
auto begin() const { return cbegin(); }
auto end() const { return cend(); }
using base_type::size;
left_type const& left() const { return *this; }
right_type const& right() const { return inverse_index; }
std::pair<const_iterator, bool> insert(value_type const& v) {
auto lr = left().find(v);
auto rr = right().find(v);
bool hasl = lr!=left().end(),
hasr = rr!=right().end();
if (!hasl && !hasr) {
auto lins = mutable_left().insert(left().lower_bound(v), v);
auto rins = mutable_right().insert(right().lower_bound(*lins), *lins);
(void) rins;
return { lins, true };
} else {
return { end(), false };
}
}
private:
detail::map_adaptor<typename Traits::right_traits> inverse_index;
left_type& mutable_left() { return *this; }
right_type& mutable_right() { return inverse_index; }
};
}
#include <iostream>
#define CHECK(cond) do {\
if (cond) { } else { std::cout << "FAILED: " #cond "\n"; } } while(false)
int main() {
using Map = bimap::bimap<int, std::string>;
Map bm;
CHECK(bm.insert(std::make_tuple(1,"three")).second);
// now left 1 and right "three" are taken:
CHECK(!bm.insert(std::make_tuple(1,"two")).second);
CHECK(!bm.insert(std::make_tuple(2,"three")).second);
// unrelated keys insert fine
CHECK(bm.insert(std::make_tuple(2,"aaaa")).second);
// thing contains 2 elements now:
CHECK(bm.size() == 2);
using std::get;
for (Map::value_type const& p : bm) std::cout << get<0>(p) << ", " << get<1>(p) << "; "; std::cout << "\n";
for (Map::value_type const& p : bm.left()) std::cout << get<0>(p) << ", " << get<1>(p) << "; "; std::cout << "\n";
// right view map orders by right index
for (Map::value_type const& p : bm.right()) std::cout << get<0>(p) << ", " << get<1>(p) << "; "; std::cout << "\n";
// you can do find, lower_bound, equal_range etc. on both sides
}
打印:
1, three; 2, aaaa;
1, three; 2, aaaa;
2, aaaa; 1, three;
这个问题以前有人问过here我承认,但现在已经是 4 年前了,所以我敢要求更新:
我需要一种方法来将 tuple/pair 添加到容器并有效地搜索左右元素。
Boost 具有 bimap
和 multi_index
,它们完全符合我的要求,但我想知道在普通现代 C++-11/14 中推荐的替代方案是什么,以防您不想引入依赖项提升(无论出于何种原因)。
链接中的一个答案表明不需要 s.th。由于 透明比较器 ,不再像双图一样。接受的答案建议将 std::map
s 与 key1
-> key2
和 key2
-> key1
.
我真的不知道透明比较器是如何帮助我的,我只是好奇是否有一些你应该怎么做以及为什么 - 解决方案。你能提供一些吗hints/links?
我认为这只是很多繁琐的工作。
为了好玩,我开始在这里实现一个起点。
备注:
- 'main' 支持是
std::list<tuple<K,V>>
。这确保 iterator/reference 失效语义尽可能接近std::map
- 主背景兼作 "left" 视图(仅供外部使用的只读形式)
- "right" 视图非常相似,但使用
std::reference_wrapper<value_type const>
所以我们只索引第一个容器,真的
我只实现了简单的查询(lower_bound
、upper_bound
、equal_range
和 find
)。当然有迭代器和 ranged-for。
您还有一些事情要做(erase
、emplace
、范围插入、initializer_list 构造;有状态 allocator/comparator 支持很粗略(没有构造函数考虑相关参数)但范围分配器已被考虑在内。)
事不宜迟:
#include <algorithm>
#include <tuple>
#include <list>
#include <functional> // for std::reference_wrapper
#include <cassert>
namespace bimap {
namespace detail {
template <typename Cmp, typename V, size_t I> struct CompareByElement : private Cmp {
bool operator()(V const& a, V const& b) const {
using std::get;
return Cmp::operator()(get<I>(a), get<I>(b));
}
bool operator()(V const& a, V const& b) {
using std::get;
return Cmp::operator()(get<I>(a), get<I>(b));
}
};
namespace tags {
struct left_view;
struct right_view;
}
template <typename ViewTag, typename Left, typename Right, typename LeftCmp, typename RightCmp, typename RawAlloc>
struct view_traits;
template <typename Left, typename Right, typename LeftCmp, typename RightCmp, typename RawAlloc>
struct view_traits<tags::left_view, Left, Right, LeftCmp, RightCmp, RawAlloc> {
using value_type = std::tuple<Left, Right>;
using allocator_type = typename RawAlloc::template rebind<value_type>::other;
using base_type = std::list<value_type, allocator_type>;
using comparator = CompareByElement<LeftCmp, value_type, 0>;
};
template <typename Left, typename Right, typename LeftCmp, typename RightCmp, typename RawAlloc>
struct view_traits<tags::right_view, Left, Right, LeftCmp, RightCmp, RawAlloc> {
using value_type = std::tuple<Left, Right>;
using allocator_type = typename RawAlloc::template rebind<std::reference_wrapper<value_type const>>::other;
using base_type = std::list<std::reference_wrapper<value_type const>, allocator_type>;
using comparator = CompareByElement<RightCmp, value_type, 1>;
};
template <typename Left, typename Right, typename LeftCmp, typename RightCmp, typename RawAlloc>
struct bimap_traits {
using left_traits = view_traits<tags::left_view, Left, Right, LeftCmp, RightCmp, RawAlloc>;
using right_traits = view_traits<tags::right_view, Left, Right, LeftCmp, RightCmp, RawAlloc>;
};
template <typename Traits> struct map_adaptor :
private Traits::base_type,
private Traits::comparator // empty base class optimization
{
using value_type = typename Traits::value_type;
using allocator_type = typename Traits::allocator_type;
using base_type = typename Traits::base_type;
using comparator = typename Traits::comparator;
using iterator = typename base_type::iterator;
using const_iterator = typename base_type::const_iterator;
using base_type::cbegin;
using base_type::cend;
using base_type::begin;
using base_type::end;
using base_type::insert;
using base_type::size;
auto lower_bound(value_type const& v) { return std::lower_bound(base_type::begin(), base_type::end(), v, get_comp()); }
auto upper_bound(value_type const& v) { return std::upper_bound(base_type::begin(), base_type::end(), v, get_comp()); }
auto equal_range(value_type const& v) { return std::equal_range(base_type::begin(), base_type::end(), v, get_comp()); }
auto lower_bound(value_type const& v) const { return std::lower_bound(base_type::begin(), base_type::end(), v, get_comp()); }
auto upper_bound(value_type const& v) const { return std::upper_bound(base_type::begin(), base_type::end(), v, get_comp()); }
auto equal_range(value_type const& v) const { return std::equal_range(base_type::begin(), base_type::end(), v, get_comp()); }
auto find(value_type const& v) {
auto er = equal_range(v);
return er.first == er.second? end() : er.first;
}
auto find(value_type const& v) const {
auto er = equal_range(v);
return er.first == er.second? end() : er.first;
}
base_type& base() { return *static_cast<base_type*>(this); }
base_type const & base() const { return *static_cast<base_type const*>(this); }
private:
comparator& get_comp() { return *this; }
comparator const& get_comp() const { return *this; }
};
}
template <typename Left, typename Right,
typename LeftCmp = std::less<Left>, typename RightCmp = std::less<Right>,
typename RawAlloc = std::allocator<void>,
typename Traits = detail::bimap_traits<Left, Right, LeftCmp, RightCmp, RawAlloc>
>
class bimap : private detail::map_adaptor<typename Traits::left_traits> {
public:
using left_type = typename detail::map_adaptor<typename Traits::left_traits>;
using right_type = typename detail::map_adaptor<typename Traits::right_traits>;
using value_type = typename Traits::left_traits::value_type;
using allocator_type = typename Traits::left_traits::allocator_type;
using base_type = left_type;
using const_iterator = typename base_type::const_iterator;
using iterator = const_iterator;
using base_type::cbegin;
using base_type::cend;
auto begin() const { return cbegin(); }
auto end() const { return cend(); }
using base_type::size;
left_type const& left() const { return *this; }
right_type const& right() const { return inverse_index; }
std::pair<const_iterator, bool> insert(value_type const& v) {
auto lr = left().find(v);
auto rr = right().find(v);
bool hasl = lr!=left().end(),
hasr = rr!=right().end();
if (!hasl && !hasr) {
auto lins = mutable_left().insert(left().lower_bound(v), v);
auto rins = mutable_right().insert(right().lower_bound(*lins), *lins);
(void) rins;
return { lins, true };
} else {
return { end(), false };
}
}
private:
detail::map_adaptor<typename Traits::right_traits> inverse_index;
left_type& mutable_left() { return *this; }
right_type& mutable_right() { return inverse_index; }
};
}
#include <iostream>
#define CHECK(cond) do {\
if (cond) { } else { std::cout << "FAILED: " #cond "\n"; } } while(false)
int main() {
using Map = bimap::bimap<int, std::string>;
Map bm;
CHECK(bm.insert(std::make_tuple(1,"three")).second);
// now left 1 and right "three" are taken:
CHECK(!bm.insert(std::make_tuple(1,"two")).second);
CHECK(!bm.insert(std::make_tuple(2,"three")).second);
// unrelated keys insert fine
CHECK(bm.insert(std::make_tuple(2,"aaaa")).second);
// thing contains 2 elements now:
CHECK(bm.size() == 2);
using std::get;
for (Map::value_type const& p : bm) std::cout << get<0>(p) << ", " << get<1>(p) << "; "; std::cout << "\n";
for (Map::value_type const& p : bm.left()) std::cout << get<0>(p) << ", " << get<1>(p) << "; "; std::cout << "\n";
// right view map orders by right index
for (Map::value_type const& p : bm.right()) std::cout << get<0>(p) << ", " << get<1>(p) << "; "; std::cout << "\n";
// you can do find, lower_bound, equal_range etc. on both sides
}
打印:
1, three; 2, aaaa;
1, three; 2, aaaa;
2, aaaa; 1, three;