Die Ordered-Map-Bibliothek stellt eine Hash-Map und einen Hash-Satz bereit, die die Einfügereihenfolge auf ähnliche Weise wie Pythons OrderedDict beibehalten. Beim Durchlaufen der Karte werden die Werte in derselben Reihenfolge zurückgegeben, in der sie eingefügt wurden.
Die Werte werden zusammenhängend in einer zugrunde liegenden Struktur gespeichert, sodass auch nach einem Löschvorgang keine Lücken zwischen den Werten entstehen. Standardmäßig wird für diese Struktur ein std::deque
verwendet, es ist jedoch auch möglich, einen std::vector
zu verwenden. Auf diese Struktur kann direkt über die Methode values_container()
zugegriffen werden. Wenn es sich bei der Struktur um einen std::vector
, wird auch eine Methode data()
bereitgestellt, um die einfache Interaktion mit C-APIs zu ermöglichen. Dies ermöglicht eine schnelle Iteration, hat jedoch den Nachteil einer O(bucket_count)-Löschoperation. Es stehen die Funktionen O(1) pop_back()
und O(1) unordered_erase()
zur Verfügung. Wenn häufig geordnetes Löschen verwendet wird, wird eine andere Datenstruktur empfohlen.
Um Kollisionen bei Hashes aufzulösen, verwendet die Bibliothek lineares Robin-Hood-Probing mit Rückwärtsverschiebungslöschung.
Die Bibliothek bietet ein Verhalten ähnlich einem std::deque/std::vector
mit eindeutigen Werten, jedoch mit einer durchschnittlichen Zeitkomplexität von O(1) für Suchvorgänge und einer amortisierten Zeitkomplexität von O(1) für Einfügungen. Dies geht mit einem etwas höheren Speicherbedarf einher (standardmäßig 8 Byte pro Bucket).
Es werden zwei Klassen bereitgestellt: tsl::ordered_map
und tsl::ordered_set
.
Hinweis : Die Bibliothek verwendet eine Zweierpotenz für die Größe ihres Buckets-Arrays, um das schnelle Modulo zu nutzen. Für eine gute Leistung ist es erforderlich, dass die Hash-Tabelle über eine gut verteilte Hash-Funktion verfügt. Wenn Sie auf Leistungsprobleme stoßen, überprüfen Sie Ihre Hash-Funktion.
tsl::ordered_map
aus CMakeLists.txt verwenden.std::unordered_map
, jedoch mit schnelleren Einfügungen und reduzierter Speichernutzung (Einzelheiten siehe Benchmark).find
mit einem anderen Typ als Key
ermöglichen (z. B. wenn Sie eine Karte haben, die std::unique_ptr<foo>
als Schlüssel verwendet, können Sie „ foo*
oder std::uintptr_t
als Schlüsselparameter verwenden find
ohne einen std::unique_ptr<foo>
zu konstruieren, siehe Beispiel).precalculated_hash
in der API).serialize/deserialize
in der API).-fno-exceptions
auf Clang und GCC, ohne /EH
-Option auf MSVC oder einfach durch Definition TSL_NO_EXCEPTIONS
). std::terminate
wird als Ersatz für die throw
-Anweisung verwendet, wenn Ausnahmen deaktiviert sind.std::unordered_map
und std::unordered_set
sehr ähnlich ist.std::unordered_map
tsl::ordered_map
versucht, eine ähnliche Schnittstelle wie std::unordered_map
zu haben, es bestehen jedoch einige Unterschiede.
RandomAccessIterator
.std::vector
und std::deque
(Einzelheiten finden Sie in der API). Wenn Sie std::vector
als ValueTypeContainer
verwenden, können Sie reserve()
verwenden, um etwas Speicherplatz vorab zuzuweisen und die Ungültigmachung der Iteratoren beim Einfügen zu vermeiden.erase()
Operation, sie hat eine Komplexität von O(bucket_count). Es gibt eine schnellere O(1)-Version unordered_erase()
, die jedoch die Einfügereihenfolge unterbricht (Einzelheiten finden Sie in der API). Ein O(1) pop_back()
ist ebenfalls verfügbar.operator==
und operator!=
sind reihenfolgeabhängig. Zwei tsl::ordered_map
mit denselben Werten, die jedoch in einer anderen Reihenfolge eingefügt werden, sind nicht gleich.operator*()
und operator->()
eine Referenz und einen Zeiger auf const std::pair<Key, T>
anstelle von std::pair<const Key, T>
zurück, wodurch der Wert T
nicht geändert werden kann. Um den Wert zu ändern, müssen Sie die value()
Methode des Iterators aufrufen, um eine veränderbare Referenz zu erhalten. Beispiel: 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
ausgelöst werden. Weitere Informationen finden Sie in der API.bucket_size
, bucket
, ...). Die Thread-Sicherheitsgarantie ist dieselbe wie bei std::unordered_map
(dh es ist möglich, mehrere gleichzeitige Leser ohne Schreiber zu haben).
Diese Unterschiede gelten auch zwischen std::unordered_set
und tsl::ordered_set
.
Sofern nicht anders angegeben, verfügen Funktionen über die starke Ausnahmegarantie, siehe Details. Im Folgenden führen wir Fälle auf, in denen diese Garantie nicht gegeben ist.
Die Garantie wird nur bereitgestellt, wenn ValueContainer::emplace_back
über die starke Ausnahmegarantie verfügt (was für std::vector
und std::deque
gilt, solange der Typ T
kein reiner Verschiebungstyp mit einem Verschiebungskonstruktor ist, der eine auslösen kann Ausnahme, siehe Details).
Die Funktionen tsl::ordered_map::erase_if
und tsl::ordered_set::erase_if
haben die Garantie nur unter den in ihrer Dokumentation aufgeführten Voraussetzungen.
Um ordered-map zu verwenden, fügen Sie einfach das Include-Verzeichnis zu Ihrem Include-Pfad hinzu. Es handelt sich um eine reine Header- Bibliothek.
Wenn Sie CMake verwenden, können Sie auch das exportierte Ziel tsl::ordered_map
aus CMakeLists.txt mit target_link_libraries
verwenden.
# 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)
Wenn das Projekt über make install
installiert wurde, können Sie auch find_package(tsl-ordered-map REQUIRED)
anstelle von add_subdirectory
verwenden.
Der Code sollte mit jedem C++11-Standard-kompatiblen Compiler funktionieren und wurde mit GCC 4.8.4, Clang 3.5.0 und Visual Studio 2015 getestet.
Zum Ausführen der Tests benötigen Sie die Boost-Testbibliothek und 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
Die API finden Sie hier.
# 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 ( '