ordered-map 库提供了一个哈希映射和一个哈希集,它们以类似于 Python 的 OrderedDict 的方式保留插入顺序。迭代映射时,值将按照插入的顺序返回。
这些值连续存储在底层结构中,即使在擦除操作之后,值之间也没有空洞。默认情况下,此结构使用std::deque
,但也可以使用std::vector
。该结构可以通过values_container()
方法直接访问,如果该结构是std::vector
,还提供data()
方法来轻松与C API交互。这提供了快速迭代,但具有 O(bucket_count) 擦除操作的缺点。 O(1) pop_back()
和 O(1) unordered_erase()
函数可用。如果经常使用有序擦除,建议使用另一种数据结构。
为了解决哈希冲突,该库使用线性罗宾汉探测和向后移位删除。
该库提供类似于std::deque/std::vector
行为,具有唯一值,但查找的平均时间复杂度为 O(1),插入的摊余时间复杂度为 O(1)。这是以较高的内存占用为代价的(默认情况下每个存储桶 8 字节)。
提供了两个类: tsl::ordered_map
和tsl::ordered_set
。
注意:该库使用 2 的幂作为其存储桶数组的大小,以利用快速模数。为了获得良好的性能,要求哈希表具有分布良好的哈希函数。如果您遇到性能问题,请检查您的哈希函数。
tsl::ordered_map
导出目标。std::unordered_map
类似,但插入速度更快并减少了内存使用量(有关详细信息,请参阅基准测试)。Key
不同的类型的find
(例如,如果您有一个使用std::unique_ptr<foo>
作为键的映射,则可以使用foo*
或std::uintptr_t
作为键参数find
而不构造std::unique_ptr<foo>
,请参阅示例)。precalculated_hash
参数)。serialize/deserialize
方法)。-fno-exceptions
选项,在 MSVC 上没有/EH
选项或简单地通过定义TSL_NO_EXCEPTIONS
)。 std::terminate
用于在禁用异常时替换throw
指令。std::unordered_map
和std::unordered_set
非常相似。std::unordered_map
相比的差异tsl::ordered_map
尝试拥有与std::unordered_map
类似的接口,但存在一些差异。
RandomAccessIterator
。std::vector
和std::deque
(有关详细信息,请参阅 API)。如果使用std::vector
作为ValueTypeContainer
,则可以使用reserve()
来预分配一些空间并避免插入时迭代器失效。erase()
操作速度较慢,复杂度为O(bucket_count)。存在更快的 O(1) 版本unordered_erase()
,但它破坏了插入顺序(有关详细信息,请参阅 API)。 O(1) pop_back()
也是可用的。operator==
和operator!=
与顺序相关。两个具有相同值但以不同顺序插入的tsl::ordered_map
比较不相等。operator*()
和operator->()
返回一个引用和一个指向const std::pair<Key, T>
而不是std::pair<const Key, T>
的指针,使得值T
不可修改。要修改值,您必须调用迭代器的value()
方法来获取可变引用。例子: tsl::ordered_map< int , int > map = {{ 1 , 1 }, { 2 , 1 }, { 3 , 1 }};
for ( auto it = map.begin(); it != map.end(); ++it) {
// it->second = 2; // Illegal
it. value () = 2 ; // Ok
}
IndexType
类模板参数引发,查看 API 了解详细信息。bucket_size
, bucket
,...)。线程安全保证与std::unordered_map
相同(即可以有多个并发读取器而没有写入器)。
这些差异也适用于std::unordered_set
和tsl::ordered_set
之间。
如果没有特别说明,函数具有很强的异常保证,请参阅详细信息。下面我们列出了不提供此保证的情况。
仅当ValueContainer::emplace_back
具有强异常保证时才提供保证(对于std::vector
和std::deque
来说都是如此,只要类型T
不是带有可能抛出异常的移动构造函数的仅移动类型例外,请参阅详细信息)。
tsl::ordered_map::erase_if
和tsl::ordered_set::erase_if
函数仅在其文档中列出的前提条件下才有保证。
要使用有序映射,只需将包含目录添加到包含路径中即可。它是一个仅包含头文件的库。
如果您使用 CMake,还可以将 CMakeLists.txt 中的tsl::ordered_map
导出目标与target_link_libraries
结合使用。
# Example where the ordered-map project is stored in a third-party directory
add_subdirectory (third-party/ordered-map)
target_link_libraries (your_target PRIVATE tsl::ordered_map)
如果项目已经通过make install
安装,您还可以使用find_package(tsl-ordered-map REQUIRED)
而不是add_subdirectory
。
该代码应适用于任何符合 C++11 标准的编译器,并已使用 GCC 4.8.4、Clang 3.5.0 和 Visual Studio 2015 进行了测试。
要运行测试,您将需要 Boost Test 库和 CMake。
git clone https://github.com/Tessil/ordered-map.git
cd ordered-map/tests
mkdir build
cd build
cmake ..
cmake --build .
./tsl_ordered_map_tests
该 API 可以在这里找到。
# include < iostream >
# include < string >
# include < cstdlib >
# include < tsl/ordered_map.h >
# include < tsl/ordered_set.h >
int main () {
tsl::ordered_map< char , int > map = {{ ' d ' , 1 }, { ' a ' , 2 }, { ' g ' , 3 }};
map. insert ({ ' b ' , 4 });
map[ ' h ' ] = 5 ;
map[ ' e ' ] = 6 ;
map. erase ( ' a ' );
// {d, 1} {g, 3} {b, 4} {h, 5} {e, 6}
for ( const auto & key_value : map) {
std::cout << " { " << key_value. first << " , " << key_value. second << " } " << std::endl;
}
map. unordered_erase ( ' b ' );
// Break order: {d, 1} {g, 3} {e, 6} {h, 5}
for ( const auto & key_value : map) {
std::cout << " { " << key_value. first << " , " << key_value. second << " } " << std::endl;
}
for ( auto it = map. begin (); it != map. end (); ++it) {
// it->second += 2; // Not valid.
it. value () += 2 ;
}
if (map. find ( ' d ' ) != map. end ()) {
std::cout << " Found 'd'. " << std::endl;
}
const std:: size_t precalculated_hash = std::hash< char >()( ' d ' );
// If we already know the hash beforehand, we can pass it as argument to speed-up the lookup.
if (map. find ( ' d ' , precalculated_hash) != map. end ()) {
std::cout << " Found 'd' with hash " << precalculated_hash << " . " << std::endl;
}
tsl::ordered_set< char , std::hash< char >, std::equal_to< char >,
std::allocator< char >, std::vector< char >> set;
set. reserve ( 6 );
set = { ' 3 ' , ' 4 ' , ' 9 ' , ' 2 ' };
set. erase ( ' 2 ' );
set. insert ( ' 1 ' );
set. insert ( '