مكتبة robin-map عبارة عن تطبيق C++ لخريطة تجزئة سريعة ومجموعة تجزئة باستخدام العنونة المفتوحة وتجزئة غطاء روبن الخطي مع حذف الإزاحة للخلف لحل التصادمات.
يتم توفير أربع فئات: tsl::robin_map
و tsl::robin_set
و tsl::robin_pg_map
و tsl::robin_pg_set
. الأولان أسرع ويستخدمان سياسة نمو قوة اثنين، ويستخدم الأخيران سياسة نمو رئيسية بدلاً من ذلك ويكونان قادرين على التعامل بشكل أفضل مع وظيفة التجزئة الضعيفة. استخدم الإصدار الأساسي إذا كانت هناك فرصة لتكرار الأنماط في الأجزاء السفلية من تجزئة الخاص بك (على سبيل المثال، إذا كنت تقوم بتخزين المؤشرات باستخدام وظيفة تجزئة الهوية). راجع سياسة النمو للحصول على التفاصيل.
يمكن العثور هنا على معيار tsl::robin_map
مقابل خرائط التجزئة الأخرى. تقدم هذه الصفحة أيضًا بعض النصائح حول بنية جدول التجزئة التي يجب أن تجربها لحالة الاستخدام الخاصة بك (مفيدة إذا كنت ضائعًا بعض الشيء مع تطبيقات جداول التجزئة المتعددة في مساحة اسم tsl
).
مكتبة الرأس فقط، ما عليك سوى إضافة دليل التضمين إلى مسار التضمين الخاص بك وستكون جاهزًا للبدء. إذا كنت تستخدم CMake، فيمكنك أيضًا استخدام الهدف المُصدَّر tsl::robin_map
من CMakeLists.txt.
جدول تجزئة سريع، تحقق من المعيار لبعض الأرقام.
دعم المفتاح/القيمة القابلة للإنشاء وغير الافتراضية.
دعم عمليات البحث غير المتجانسة مما يسمح باستخدام find
بنوع مختلف عن Key
(على سبيل المثال، إذا كان لديك خريطة تستخدم std::unique_ptr<foo>
كمفتاح، فيمكنك استخدام foo*
أو std::uintptr_t
كمعلمة رئيسية لـ find
دون إنشاء std::unique_ptr<foo>
، انظر المثال).
لا حاجة لحجز أي قيمة خافرة من المفاتيح.
إمكانية تخزين قيمة التجزئة إلى جانب قيمة المفتاح المخزنة لإعادة الصياغة والبحث بشكل أسرع إذا كان حساب التجزئة أو الوظائف المتساوية للمفتاح باهظ الثمن. لاحظ أنه قد يتم تخزين التجزئة حتى إذا لم يُطلب منك ذلك بشكل صريح عندما تتمكن المكتبة من اكتشاف أنه لن يكون لها أي تأثير على حجم البنية في الذاكرة بسبب المحاذاة. راجع معلمة قالب StoreHash للحصول على التفاصيل.
إذا كانت التجزئة معروفة قبل البحث، فمن الممكن تمريرها كمعلمة لتسريع عملية البحث (راجع معلمة precalculated_hash
في API).
دعم التسلسل وإلغاء التسلسل الفعال (راجع المثال وطرق serialize/deserialize
في واجهة برمجة التطبيقات للحصول على التفاصيل).
يمكن استخدام المكتبة مع تعطيل الاستثناءات (من خلال خيار -fno-exceptions
في Clang وGC، بدون خيار /EH
على MSVC أو ببساطة عن طريق تحديد TSL_NO_EXCEPTIONS
). يتم استخدام std::terminate
لاستبدال تعليمات throw
عند تعطيل الاستثناءات.
واجهة برمجة التطبيقات (API) تشبه إلى حد كبير std::unordered_map
و std::unordered_set
.
std::unordered_map
يحاول tsl::robin_map
الحصول على واجهة مشابهة لـ std::unordered_map
، ولكن توجد بعض الاختلافات.
يتم تطبيق ضمان الاستثناء القوي فقط إذا كانت العبارة التالية صحيحة std::is_nothrow_swappable<value_type>::value && std::is_nothrow_move_constructible<value_type>::value
(حيث تكون value_type
هي Key
لـ tsl::robin_set
و std::pair<Key, T>
لـ tsl::robin_map
). بخلاف ذلك، إذا تم طرح استثناء أثناء المبادلة أو النقل، فقد ينتهي الأمر بالبنية في حالة غير محددة. لاحظ أنه وفقًا للمعيار، فإن value_type
مع مُنشئ نسخة noexcept ومنشئ no move يفي أيضًا بهذا الشرط وبالتالي سيضمن ضمان الاستثناء القوي للبنية (راجع واجهة برمجة التطبيقات للحصول على التفاصيل).
يجب أن يكون النوع Key
وكذلك T
في حالة الخريطة قابلين للتبديل. ويجب أيضًا أن تكون قابلة للنسخ و/أو النقل.
لا يتصرف إبطال المكرر بنفس الطريقة، وأي عملية لتعديل جدول التجزئة تؤدي إلى إبطالها (راجع واجهة برمجة التطبيقات لمزيد من التفاصيل).
يتم إبطال المراجع والمؤشرات الخاصة بالمفاتيح أو القيم في الخريطة بنفس طريقة تكرار قيم المفاتيح هذه.
بالنسبة لمكررات tsl::robin_map
، operator*()
operator->()
بإرجاع مرجع ومؤشر إلى const std::pair<Key, T>
بدلاً من std::pair<const Key, T>
مما يجعل القيمة T
غير قابل للتعديل. لتعديل القيمة عليك استدعاء طريقة value()
للمكرر للحصول على مرجع قابل للتغيير. مثال:
tsl::robin_map<int, int> Map = {{1, 1}, {2, 1}, {3, 1}};for(auto it =map.begin(); it !=map.end() ++it) {//it->ثانية = 2; // Illegalit.value() = 2; // نعم}
لا يوجد دعم لبعض الطرق المتعلقة بالمجموعات (مثل bucket_size
، bucket
، ...).
تنطبق هذه الاختلافات أيضًا بين std::unordered_set
و tsl::robin_set
.
ضمانات سلامة الخيط هي نفسها std::unordered_map/set
(أي من الممكن أن يكون لديك عدة قراء بدون كاتب).
تدعم المكتبة سياسات النمو المتعددة من خلال معلمة قالب GrowthPolicy
. توفر المكتبة ثلاث سياسات ولكن يمكنك بسهولة تنفيذ سياساتك الخاصة إذا لزم الأمر.
tsl::rh::power_of_two_growth_policy. السياسة الافتراضية المستخدمة بواسطة tsl::robin_map/set
. تحافظ هذه السياسة على حجم مصفوفة الجرافة الخاصة بجدول التجزئة إلى قوة اثنين. يسمح هذا القيد للسياسة بتجنب استخدام عملية modulo البطيئة لتعيين تجزئة إلى مجموعة، بدلاً من hash % 2 n
، فإنها تستخدم hash & (2 n - 1)
(راجع modulo السريع). سريع ولكن هذا قد يتسبب في الكثير من التصادمات مع وظيفة التجزئة الضعيفة حيث أن المودولو الذي يتمتع بقوة اثنين يخفي فقط البتات الأكثر أهمية في النهاية.
tsl::rh::prime_growth_policy. السياسة الافتراضية المستخدمة بواسطة tsl::robin_pg_map/set
. تحافظ السياسة على حجم مصفوفة المجموعة الخاصة بجدول التجزئة إلى رقم أولي. عند تعيين تجزئة إلى مجموعة، فإن استخدام رقم أولي كمعيار سيؤدي إلى توزيع أفضل للتجزئة عبر المجموعات حتى مع وجود وظيفة تجزئة ضعيفة. للسماح للمترجم بتحسين تشغيل الوحدة النمطية، تستخدم السياسة جدول بحث يحتوي على وحدات أولية ثابتة (راجع واجهة برمجة التطبيقات لمزيد من التفاصيل). أبطأ من tsl::rh::power_of_two_growth_policy
ولكنه أكثر أمانًا.
tsl::rh::mod_growth_policy. تعمل السياسة على توسيع الخريطة من خلال عامل نمو قابل للتخصيص تم تمريره في المعلمة. ثم يستخدم فقط عامل التشغيل modulo لتعيين التجزئة إلى مجموعة. أبطأ ولكن أكثر مرونة.
لتنفيذ سياستك الخاصة، عليك تنفيذ الواجهة التالية.
struct custom_policy {// يُستدعى عند إنشاء جدول التجزئة وإعادة صياغته، min_bucket_count_in_out هو الحد الأدنى للمجموعات // التي يحتاجها جدول التجزئة. يمكن للسياسة تغييرها إلى عدد أكبر من المجموعات إذا لزم الأمر // وسيستخدم جدول التجزئة هذه القيمة كعدد مجموعات. إذا تم طلب 0 مجموعة، فيجب أن تظل القيمة // عند 0.explicit custom_policy(std::size_t& min_bucket_count_in_out); // قم بإرجاع الدلو [0، Bucket_count()) الذي ينتمي إليه التجزئة. // إذا كانت قيمة Bucket_count() تساوي 0، فيجب أن تُرجع دائمًا 0.std::size_t Bucket_for_hash(std::size_t hash) const noexcept; // إرجاع عدد المجموعات التي يجب استخدامها في النمو التالي::size_t next_bucket_count() const; // الحد الأقصى لعدد المجموعات التي تدعمها Policystd::size_t max_bucket_count() const; // إعادة تعيين سياسة النمو كما لو تم إنشاء السياسة بعدد دلو قدره 0.// بعد المسح، يجب أن ترجع السياسة دائمًا 0 عندما يتم استدعاء Bucket_for_hash().void Clear() noexcept; }
لاستخدام robin-map، ما عليك سوى إضافة دليل التضمين إلى مسار التضمين الخاص بك. إنها مكتبة رأسية فقط .
إذا كنت تستخدم CMake، فيمكنك أيضًا استخدام الهدف المُصدَّر tsl::robin_map
من CMakeLists.txt مع target_link_libraries
.
# مثال حيث يتم تخزين مشروع robin-map في دليل جهة خارجيةadd_subdirectory(third-party/robin-map)target_link_libraries(your_target PRIVATE tsl::robin_map)
إذا تم تثبيت المشروع من خلال make install
، فيمكنك أيضًا استخدام find_package(tsl-robin-map REQUIRED)
بدلاً من add_subdirectory
.
المكتبة متاحة في vcpkg وconan. كما أنه موجود في مستودعات حزم Debian وUbuntu وFedora.
يجب أن تعمل التعليمات البرمجية مع أي مترجم متوافق مع معيار C++ 17.
لإجراء الاختبارات، ستحتاج إلى مكتبة Boost Test وCMake.
استنساخ بوابة https://github.com/Tessil/robin-map.gitcd robin-map/tests بناء mkdir buildcd كميك .. cmake --build ../tsl_robin_map_tests
يمكن العثور على واجهة برمجة التطبيقات هنا.
لم يتم توثيق كافة التوابع بعد، لكنها تكرر سلوك تلك الموجودة في std::unordered_map
و std::unordered_set
، إلا إذا تم تحديد خلاف ذلك.
#include <cstdint>#include <iostream>#include <string>#include <tsl/robin_map.h>#include <tsl/robin_set.h>int main() { tsl::robin_map<std::string, int> Map = {{"a"، 1}، {"b"، 2}}; خريطة ["ج"] = 3؛ خريطة ["د"] = 4؛ Map.insert({"e"، 5}); Map.erase("ب"); for(auto it =map.begin(); it!=map.end(); ++it) {//it->secure += 2; // غير صالح.it.value() += 2; } // {د، 6} {أ، 3} {ه، 7} {ج، 5}for(const auto& key_value : Map) { std::cout << "{" << key_value.first << "، " << key_value. Second << "}" << std::endl; } إذا (map.find("a") != Map.end()) { std::cout << "تم العثور على "a"." << ستد::endl; } const std::size_t precalculated_hash = std::hash<std::string>()("a");// إذا كنا نعرف التجزئة مسبقًا، فيمكننا تمريرها في المعلمة لتسريع lookups.if( Map.find("a"، precalculated_hash) != Map.end()) { std::cout << "تم العثور على "a" مع التجزئة " << precalculated_hash << "." << ستد::endl; } /* * قد يكون حساب التجزئة ومقارنة سلسلتين std::string بطيئًا. * يمكننا تخزين تجزئة كل سلسلة std::string في خريطة التجزئة لجعل عمليات الإدراج وعمليات البحث أسرع عن طريق ضبط StoreHash على true. */ tsl::robin_map<std::string, int, std::hash<std::string>, std::equal_to<std::string>, std::allocator<std::pair<std::string, int>>, true> Map2; Map2["a"] = 1; Map2["b"] = 2; // {أ، 1} {ب، 2}for(const auto& key_value: Map2) { std::cout << "{" << key_value.first << "، " << key_value. Second << "}" << std::endl; } tsl::robin_set<int> set; set.insert({1, 9, 0}); set.insert({2, -1, 9}); // {0} {1} {2} {9} {-1}for(const auto& key : set) { std::cout << "{" << مفتاح << "}" << std::endl; } }
تسمح الأحمال الزائدة غير المتجانسة باستخدام أنواع أخرى غير Key
لعمليات البحث والمسح طالما أن الأنواع المستخدمة قابلة للتجزئة وقابلة للمقارنة مع Key
.
لتنشيط الأحمال الزائدة غير المتجانسة في tsl::robin_map/set
، يجب أن يكون المعرف المؤهل KeyEqual::is_transparent
صالحًا. إنه يعمل بنفس الطريقة كما في std::map::find
. يمكنك إما استخدام std::equal_to<>
أو تحديد كائن الوظيفة الخاص بك.
يجب أن يكون كل من KeyEqual
و Hash
قادرين على التعامل مع الأنواع المختلفة.
#include <functional>#include <iostream>#include <string>#include <tsl/robin_map.h>struct الموظف {employee(int id, std::string name): m_id(id), m_name(std::move) (اسم)) { } // إما أن نقوم بتضمين المقارنات في الفصل ونستخدم `std::equal_to<>`...friend booloperator==(const member& empl, int emmpl_id) {return empl.m_id == empl_id; } عامل تشغيل صديق منطقي ==(int emmpl_id, const member& empl) {return emmpl_id == empl.m_id; } صديق عامل التشغيل المنطقي ==(const موظف& emmpl1, const موظف& emmpl2) {return emmpl1.m_id == emmpl2.m_id; } كثافة العمليات m_id; ستد::سلسلة m_name؛ };// ... أو نطبق فئة منفصلة لمقارنة الموظفين.structequal_employee {using is_transparent = void; عامل منطقي ()(const member & empl, int emmpl_id) const {return emmpl.m_id == emmpl_id; } عامل منطقي ()(int emmpl_id, const member& empl) const {return emmpl_id == empl.m_id; } عامل منطقي ()(const موظف& emmpl1، موظف ثابت& emmpl2) const {return emmpl1.m_id == emmpl2.m_id; } };هيكل hash_employee { std::size_t عامل التشغيل()(const member& empl) const {return std::hash<int>()(empl.m_id); } std::size_toperator()(int id) const {return std::hash<int>()(id); } };int main() {// استخدم std::equal_to<> الذي سيستنتج المعلمات ويعيد توجيهها تلقائيًاtsl::robin_map<employee, int, hash_employee, std::equal_to<>> Map; Map.insert({employee(1, "John Doe"), 2001}); Map.insert({employee(2, "Jane Doe"), 2002}); Map.insert({employee(3, "John Smith"), 2003});// John Smith 2003auto it = Map.find(3);if(it != Map.end()) { std::cout << it->first.m_name << " " << it->ثاني << std::endl; } Map.erase(1);// استخدم KeyEqual مخصصًا يحتوي على عضو is_transparent typetsl::robin_map<employee, int, hash_employee,equal_employee> Map2; Map2.insert({employee(4, "Johnny Doe"), 2004});// 2004std::cout << Map2.at(4) << std::endl; }
توفر المكتبة طريقة فعالة لإجراء تسلسل وإلغاء تسلسل خريطة أو مجموعة بحيث يمكن حفظها في ملف أو إرسالها عبر الشبكة. للقيام بذلك، يتطلب الأمر من المستخدم توفير كائن دالة لكل من التسلسل وإلغاء التسلسل.
تسلسل البنية {// يجب أن يدعم الأنواع التالية لـ U: std::int16_t, std::uint32_t, // std::uint64_t, float and std::pair<Key, T> إذا تم استخدام خريطة أو مفتاح لـ / / set.template<typename U>void عامل التشغيل()(const U& value); };
إلغاء تسلسل البنية {// يجب أن يدعم الأنواع التالية لـ U: std::int16_t, std::uint32_t, // std::uint64_t, float and std::pair<Key, T> إذا تم استخدام خريطة أو مفتاح لـ / / مجموعة.قالب<typename U> مشغل U()(); };
لاحظ أن التنفيذ يترك التوافق الثنائي (endianness، التمثيل الثنائي العائم، حجم int، ...) للأنواع التي يقوم بتسلسلها/إلغاء تسلسلها في أيدي كائنات الوظيفة المتوفرة إذا كان التوافق مطلوبًا.
يمكن العثور على مزيد من التفاصيل بشأن طرق serialize
deserialize
في واجهة برمجة التطبيقات.
#include <cassert>#include <cstdint>#include <fstream>#include <type_traits>#include <tsl/robin_map.h>مُسلسل الفئة {public:serializer(const char* file_name) { m_ostream.exceptions(m_ostream.badbit | m_ostream.failbit); m_ostream.open(file_name, std::ios::binary); } template<class T, typename std::enable_if<std::is_arithmetic<T>::value>::type* = nullptr>void عامل()(const T& value) { m_ostream.write(reinterpret_cast<const char*>(&value), sizeof(T)); } عامل التشغيل void()(const std::pair<std::int64_t, std::int64_t>& value) { (*هذا)(value.first); (*هذا)(القيمة.الثانية); }private:std::ofstream m_ostream; };إلغاء تسلسل الفئة {public:deserializer(const char* file_name) { m_istream.exceptions(m_istream.badbit | m_istream.failbit | m_istream.eofbit); m_istream.open(file_name, std::ios::binary); } القالب<الفئة T> عامل T () () { قيمة T؛ إلغاء التسلسل (القيمة)؛ قيمة الإرجاع؛ } خاص: قالب <class T، اسم النوع std::enable_if<std::is_arithmetic<T>::value>::type* = nullptr>void deserialize(T& value) { m_istream.read(reinterpret_cast<char*>(&value), sizeof(T)); } void إلغاء التسلسل(std::pair<std::int64_t, std::int64_t>& value) {deserialize(value.first);deserialize(value. Second); }private:std::ifstream m_istream; };int main() {const tsl::robin_map<std::int64_t, std::int64_t> خريطة = {{1, -1}, {2, -2}, {3, -3}, {4, -4}}; const char* file_name = "robin_map.data"; { المسلسل التسلسلي (اسم_الملف) ؛ Map.serialize(serial); } { إلغاء التسلسل dserial(file_name);auto Map_deserialized = tsl::robin_map<std::int64_t, std::int64_t>::deserialize(dserial); تأكيد(خريطة == Map_deserialized); } { إلغاء التسلسل dserial(file_name); /** * إذا كانت الخريطة المتسلسلة وإلغاء التسلسل متوافقة مع التجزئة (راجع الشروط في واجهة برمجة التطبيقات)، * تعيين الوسيطة على تسريع عملية إلغاء التسلسل لأننا لا نملك * لإعادة حساب تجزئة كل مفتاح. نحن نعلم أيضًا مقدار المساحة التي يحتاجها كل دلو. */const bool hash_compatible = true;auto Map_deserialized = tsl::robin_map<std::int64_t, std::int64_t>::deserialize(dserial, hash_compatible); تأكيد(خريطة == Map_deserialized); } }
من الممكن استخدام مكتبة التسلسل لتجنب النموذج المعياري.
يستخدم المثال التالي Boost Serialization مع تدفق ضغط Boost zlib لتقليل حجم الملف المتسلسل الناتج. يتطلب المثال لغة C++20 نظرًا لاستخدام بنية قائمة معلمات القالب في lambdas، ولكن يمكن تكييفها مع الإصدارات الأقل حداثة.
#include <boost/archive/binary_iarchive.hpp>#include <boost/archive/binary_oarchive.hpp>#include <boost/iostreams/filter/zlib.hpp>#include <boost/iostreams/filtering_stream.hpp>#include <boost /serialization/split_free.hpp>#include <boost/serialization/utility.hpp>#include <cassert>#include <cstdint>#include <fstream>#include <tsl/robin_map.h>namespace boost { namespace serialization {template<class Archive, class Key, class T> تسلسل باطلة (الأرشيف وar، tsl::robin_map<Key, T>&map، إصدار int غير موقع) {split_free(ar,map,version); }template<class Archive, class Key, class T>void save(Archive & ar, const tsl::robin_map<Key, T>&map, const unsigned int /*version*/) {auto serializer = [&ar](const تلقائي& v) { ar & v; }; Map.serialize(serializer); }template<class Archive, class Key, class T>voidload(Archive & ar, tsl::robin_map<Key, T>&map, const unsigned int /*version*/) {auto deserializer = [&ar]<typename U >() { يو يو؛ ع & ش؛ ارجع ش؛ }; Map = tsl::robin_map<Key, T>::deserialize(deserializer); } }}كثافة العمليات الرئيسية () { tsl::robin_map<std::int64_t, std::int64_t> Map = {{1, -1}, {2, -2}, {3, -3}, {4, -4}}; const char* file_name = "robin_map.data"; { الأمراض المنقولة جنسيا::ofstream ofs; ofs.exceptions(ofs.badbit | ofs.failbit); ofs.open(file_name, std::ios::binary); Boost::iostreams::filtering_ostream fo; fo.push(boost::iostreams::zlib_compressor()); fo.push(ofs); Boost::archive::binary_oarchive oa(fo); الزراعة العضوية << الخريطة؛ } { ستد::ifstream إذا؛ ifs.exceptions(ifs.badbit | ifs.failbit | ifs.eofbit); ifs.open(file_name, std::ios::binary); Boost::iostreams::filtering_istream fi; fi.push(boost::iostreams::zlib_decompressor()); fi.push(ifs); Boost::archive::binary_iarchive ia(fi); tsl::robin_map<std::int64_t, std::int64_t> Map_deserialized; ia >> Map_deserialized; تأكيد(خريطة == Map_deserialized); } }
تجدر الإشارة إلى اثنين من مخاطر الأداء المحتملة التي تتضمن tsl::robin_map
و tsl::robin_set
:
تجزئة سيئة . يمكن أن تؤدي وظائف التجزئة التي تنتج العديد من التصادمات إلى السلوك المفاجئ التالي: عندما يتجاوز عدد التصادمات حدًا معينًا، سيتم توسيع جدول التجزئة تلقائيًا لإصلاح المشكلة. ومع ذلك، في الحالات المتدهورة، قد لا يكون لهذا التوسع أي تأثير على عدد التصادمات، مما يتسبب في وضع الفشل حيث يؤدي التسلسل الخطي للإدراج إلى نمو التخزين الأسي.
تمت ملاحظة هذه الحالة بشكل أساسي عند استخدام إستراتيجية النمو الافتراضية لقوة اثنين مع STL std::hash<T>
الافتراضي للأنواع الحسابية T
، والتي غالبًا ما تكون هوية! انظر العدد رقم 39 مثالا. الحل بسيط: استخدم دالة تجزئة أفضل و/أو tsl::robin_pg_set
/ tsl::robin_pg_map
.
محو العنصر وعوامل التحميل المنخفضة . يعكس tsl::robin_map
و tsl::robin_set
واجهة برمجة تطبيقات STL Map/set، التي تكشف عن طريقة iterator erase(iterator)
تزيل عنصرًا في موضع معين، وتعيد مكررًا صالحًا يشير إلى العنصر التالي.
يتطلب إنشاء كائن التكرار الجديد هذا الانتقال إلى المجموعة غير الفارغة التالية في الجدول، والتي يمكن أن تكون عملية مكلفة عندما يكون لجدول التجزئة عامل تحميل منخفض (على سبيل المثال، عندما تكون capacity()
أكبر بكثير من size()
).
علاوة على ذلك، لا تقوم طريقة erase()
أبدًا بتقليص الجدول وإعادة تجزئته لأن هذا غير مسموح به بموجب مواصفات هذه الوظيفة. يمكن أن يؤدي التسلسل الخطي لعمليات الإزالة العشوائية دون عمليات الإدراج الوسيطة إلى حالة متدهورة بتكلفة وقت تشغيل تربيعية.
في مثل هذه الحالات، غالبًا لا تكون هناك حاجة إلى قيمة إرجاع مكرر، وبالتالي فإن التكلفة غير ضرورية على الإطلاق. وبالتالي، يوفر كل من tsl::robin_set
و tsl::robin_map
طريقة محو بديلة void erase_fast(iterator)
التي لا تُرجع مكررًا لتجنب الاضطرار إلى العثور على العنصر التالي.
الكود مرخص بموجب ترخيص MIT، راجع ملف الترخيص للحصول على التفاصيل.