हे बूस्ट मल्टी-इंडेक्स कंटेनर

मैं आपको इस तरह के एक अद्भुत बूस्ट मॉड्यूल को मल्टी_इंडेक्स_कंटेनर के रूप में पेश करना या याद दिलाना चाहता हूं। हम सभी जानते हैं और सूची, वैक्टर, मानचित्र जैसे डेटा को संग्रहीत करने और एक्सेस करने के लिए मानक एसटीएल कक्षाओं का गहन उपयोग करते हैं। उनके बारे में बहुत कुछ कहा जा चुका है और उनके आवेदनों की सभी विशेषताओं की जांच की जा चुकी है।

एक से अधिक कुंजी या उनके संयोजनों का उपयोग करके वस्तुओं को संग्रहीत और एक्सेस करने के लिए एक वर्ग की आवश्यकता होने पर चीजें जटिल हो जाती हैं। आमतौर पर वे दो नक्शे या हैश बनाकर शुरू करते हैं, फिर उनकी संख्या में वृद्धि के साथ सब कुछ अधिक जटिल हो जाता है और इन हैश को सिंक्रनाइज़ करने, डालने, हटाने और नाम बदलने के लिए कोड के अधिक जटिल खंड होते हैं और इस या उस ऑपरेशन की लागत को समझना काफी मुश्किल हो जाता है। और निश्चित रूप से, ऐसी साइकिलों का निर्माण बड़ी संख्या में कीड़े, अनुकूलन की आवश्यकता और इतने पर होता है।

बेशक, हमारे सामने सब कुछ पहले से ही आविष्कार किया गया है और बूस्ट लाइब्रेरी में पहले से ही इन समस्याओं को हल करने के लिए एक मॉड्यूल है - बढ़ावा :: मल्टी_इंडेक्स। एक बहुत बड़ा लाभ गति है, मल्टी_इंडेक्स बहुत तेज है। हालाँकि, इस मॉड्यूल का दस्तावेज़ीकरण है, मान लें कि इसे समझना मुश्किल है, और शुरुआती लोग इस मॉड्यूल को बायपास करने का प्रयास करते हैं। और निश्चित रूप से, हम बहु_index_container के साथ काम करते समय त्रुटियों के लिए संकलक संदेशों के बारे में अलग से कह सकते हैं - हर कोई टेम्पलेट्स के बारे में लंबे संदेश का पता नहीं लगा सकता है।

हम इस अंतराल को भरने और एक गर्म शुरुआत और इस शक्तिशाली उपकरण के उपयोग के लिए सरल उदाहरण दिखाने की कोशिश करेंगे। मैं उदाहरणों में Qt के साथ थोड़ा प्रयोग करूँगा। (बस अपने स्वयं के टेम्पलेट सिस्टम के साथ क्यूटी में, मैं अक्सर मल्टीमाइंडेक्स के लिए एक आदिम तुलनात्मक याद करता हूं)


मान लीजिए कि हमारे पास खेतों के साथ एक छोटी संरचना है:
struct Rec { QString name, phone, addr; }; 

multi_index कुंजी के लिए टैग्स (गोदी में टैग) का उपयोग करने का एक बहुत ही सुविधाजनक अवसर देता है।
हम उन्हें आरईसी में सीधे परिभाषित करते हैं ताकि वे कोड के साथ रेंग न सकें:
 struct Rec { QString name, phone, addr; struct ByName {}; struct ByPhone {}; struct ByAddr {}; }; 

हम निम्न सरणी बना सकते हैं:
 typedef boost::multi_index_container<Rec, indexed_by< ordered_unique< tag<Rec::ByName>, member<Rec,QString,&Rec::name> >, ordered_non_unique< tag<Rec::ByPhone>, member<Rec,QString,&Rec::phone> >, ordered_non_unique< tag<Rec::ByAddr>, member<Rec,QString,&Rec::addr> > > > Store; 

हम स्वीकार करते हैं कि हमारे रिकॉर्ड नाम (नाम) द्वारा अद्वितीय हैं, लेकिन पते और फोन द्वारा अद्वितीय नहीं हैं। नाम की विशिष्टता इस तथ्य में भी निहित है कि हम सरणी में एक ही नाम के साथ दो रिकॉर्ड नहीं जोड़ सकते।
 { Store store; Rec r1 = { "Basilio Pupkinio", "022", "Neron st" }; qDebug() << "ok1" << store.insert(r1).second; // ok1 true qDebug() << "ok2" << store.insert(r1).second; // ok2 false } 

मेरे लिए यह एक महत्वपूर्ण सुविधा है कि सरणी खुद डुप्लिकेट की उपस्थिति की अनुमति नहीं देगी और जब मैं इसे एक्सेस करता हूं, तो मैं सामग्री की शुद्धता पर प्राथमिकता दे सकता हूं।

नाम से प्रविष्टि खोजें:
 { QString find_id = "Basilio Pupkinio"; typedef Store::index<Rec::ByName>::type List; const List & ls = store.get<Rec::ByName>(); List::const_iterator it = ls.find(find_id); if ( it != ls.end() ) { qDebug() << (*it).addr; // "Neron st" } } 

अगर हमारे पास फोन 022 के साथ कई रिकॉर्ड हैं
 { Store store; Rec r1 = { "Basilio Pupkinio", "022", "Pushkina st" }; Rec r2 = { "Vasya Pupkin", "022", "Around st" }; store.insert(r1) store.insert(r2) } 

फिर फोन 022 के साथ सभी रिकॉर्ड्स को ढूंढें
 { QString find_phone = "022"; Store::index<Rec::ByPhone>::type::iterator it0, it1; tie(it0,it1) = store.get<Rec::ByPhone>().equal_range(find_phone); while(it0 != it1) { qDebug() << (*it0).name; ++it0; } } 

क्या होगा यदि हम फोन और पते के संयोजन से खोज करने में रुचि रखते हैं?
हम इस ब्लॉक के साथ अपने सरणी में एक समग्र सूचकांक जोड़ सकते हैं:
  ordered_non_unique< tag<Rec::ByKey>, composite_key< Rec, member<Rec,QString,&Rec::phone>, member<Rec,QString,&Rec::addr> > >, 

(प्लस लेबल संरचना को जोड़ना न भूलें ByKey {};)
 { Rec r1 = { "Basilio Pupkinio", "022", "Pushkina st" }; Rec r2 = { "Vasya Pupkin", "022", "Around st" }; Rec r3 = { "Vasilisa Pupkina", "022", "Around st" }; store.insert(r1); store.insert(r2); store.insert(r3); { QString find_phone = "022"; QString find_addr = "Around st"; Store::index<Rec::ByKey>::type::iterator it0, it1; tie(it0,it1) = store.get<Rec::ByKey>().equal_range(make_tuple(find_phone, find_addr)); while(it0 != it1) { qDebug() << (*it0).name; ++it0; } } } 

फिर, हम समझते हैं कि मिश्रित कुंजियों के लिए हम order_non_unique और order_unique दोनों का उपयोग कर सकते हैं।
इस तरह, अनुमत कुंजियों की सामग्री और उनके संयोजन पर कुछ अतिरिक्त शर्तों को लागू करना बहुत सुविधाजनक है - सरणी खुद हमें "गलत" वस्तुओं को जोड़ने की अनुमति नहीं देगी।
यदि हम एक वेक्टर के रूप में एक साथ सरणी तक पहुंच की क्षमता चाहते हैं, तो हम आसानी से random_access जोड़ सकते हैं:
 typedef boost::multi_index_container<Rec, indexed_by< random_access<>, ordered_unique< tag<Rec::ByName>, member<Rec,QString,&Rec::name> >, ordered_non_unique< tag<Rec::ByPhone>, member<Rec,QString,&Rec::phone> > > > Store; 

फिर हमारे पास स्टोर [0], आदि के रूप में एक्सेस करने की क्षमता है, push_back ()।
यदि हम O (1) कुंजी द्वारा त्वरित पहुंच के लिए सरणी को हैश के रूप में उपयोग करना चाहते हैं, लेकिन डालने / हटाने के द्वारा O (लॉग (n)) की तुलना में धीमी गति से, तो हम क्रमबद्ध_ऑनिक / ऑर्डर_नोन_युनी के बजाय hashed_unique / hashed_non_unique का उपयोग कर सकते हैं:
 typedef boost::multi_index_container<Rec, indexed_by< hashed_non_unique< tag<Rec::ByName>, member<Rec,QString,&Rec::name> >, ordered_non_unique< tag<Rec::ByPhone>, member<Rec,QString,&Rec::phone> > > > Store; std::size_t hash_value(QString x) { return qHash(x); } 

आप समग्र कुंजियों के लिए hashed_uniqie / hashed_non_unique का भी उपयोग कर सकते हैं।

अभिलेखों के संशोधन के बारे में।
चूंकि ऑब्जेक्ट्स के फ़ील्ड को सरणी के सूचक के साथ सिंक्रनाइज़ किया जाना चाहिए, आप केवल ऑब्जेक्ट के फ़ील्ड को बदल नहीं सकते हैं। मान लीजिए कि हमें प्रविष्टियों में से एक के लिए फोन बदलने की आवश्यकता है। हमारी कुंजियों को ऑब्जेक्ट्स के साथ सिंक्रोनाइज़ करने के लिए, यह परिवर्तन एक फ़नकार द्वारा किया जाना चाहिए:
 struct Rec { QString name, phone, addr; struct ByName {}; struct ByPhone {}; struct ByAddr {}; struct PhoneChange : public std::unary_function<Rec,void> { QString p; PhoneChange(const QString &_p) : p(_p) {} void operator()(Rec & r) { r.phone = p; } }; }; 

हम नाम से सरणी में एक खोज कर सकते हैं, नाम से एक इटरेटर प्राप्त कर सकते हैं, प्रोजेक्ट का उपयोग कर सकते हैं () इसे फोन से इट्रेटर में अनुवाद कर सकते हैं और एक फ़नकार के माध्यम से ऑब्जेक्ट और सरणी को संशोधित कर सकते हैं:
 { Store store; Rec r1 = { "Basilio Pupkinio", "021", "Around st" }; Rec r2 = { "Vasya Pupkin", "022", "Around st" }; Rec r3 = { "Vasilisa Pupkina", "022", "Around st" }; store.insert(r1); store.insert(r2); store.insert(r3); QString find_id = "Basilio Pupkinio"; typedef Store::index<Rec::ByName>::type NList; typedef Store::index<Rec::ByPhone>::type PList; NList & ns = store.get<Rec::ByName>(); PList & ps = store.get<Rec::ByPhone>(); NList::const_iterator nit = ns.find(find_id); if ( nit != ns.end() ) { PList::const_iterator pit = store.project<Rec::ByPhone>(nit); ps.modify(pit, Rec::PhoneChange("022")); } } 

इंडेक्स का उपयोग केवल फ़ील्ड तक सीमित नहीं है - आप क्लास के तरीकों का भी उपयोग कर सकते हैं, उदाहरण के लिए:
 struct Rec { QString n, phone, addr; QString name() const { return n; } struct ByName {}; struct ByPhone {}; }; typedef boost::multi_index_container<Rec, indexed_by< hashed_unique< tag<Rec::ByName>, const_mem_fun<Rec,QString,&Rec::name> >, ordered_non_unique< tag<Rec::ByPhone>, member<Rec,QString,&Rec::phone> > > > Store; 

हमें संकेत के उपयोग का भी उल्लेख करना चाहिए:
 struct Rec { QString n, phone, addr; QString name() const { return n; } struct ByName {}; struct ByPhone {}; }; typedef boost::shared_ptr<Rec> Rec_ptr; typedef boost::multi_index_container<Rec_ptr, indexed_by< hashed_non_unique< tag<Rec::ByName>, const_mem_fun<Rec,QString,&Rec::name> >, ordered_non_unique< tag<Rec::ByPhone>, member<Rec,QString,&Rec::phone> >, hashed_non_unique< tag<Rec::ByKey>, composite_key< Rec, member<Rec,QString,&Rec::phone>, member<Rec,QString,&Rec::addr> > >, ordered_non_unique< tag<Rec::ByAddr>, member<Rec,QString,&Rec::addr> > > > Store; 

बाकी सभी तर्क लगभग एक ही (केवल - एक-दो स्थानों पर) बने हुए हैं।
 { QString find_phone = "022"; Store::index<Rec::ByPhone>::type::iterator it0, it1; tie(it0,it1) = store.get<Rec::ByPhone>().equal_range(find_phone); while(it0 != it1) { qDebug() << (*it0)->name(); ++it0; } } 

प्लस यह एक ब्लॉक उदाहरण के लिए जोड़ने के लायक है
  ordered_unique< tag<Rec::ByPtr>, const_mem_fun<Rec_ptr,Rec *,&Rec_ptr::get> >, 

डुप्लिकेट पॉइंटर्स को जोड़ने के लिए बाहर करना।

नतीजतन, डेटा संरचनाओं का निर्माण करना संभव है जो कि स्मार्ट पॉइंटर्स द्वारा पहुंच और इंटरकनेक्टेड में बहुत कुशल हैं, जहां प्रत्येक ऑपरेशन के लिए एक या किसी अन्य डेटा तक पहुंचने की लागत की भविष्यवाणी करना काफी आसान है। एक अर्थ में, मल्टी_इंडेक्स_कंटेनर को पूर्वनिर्धारित कुंजी का उपयोग करने के लिए एक बहुत तेज़ इन-मेमोरी डेटाबेस टेबल के रूप में सोचा जा सकता है।

मैं सरल उदाहरणों के साथ आशा करता हूं कि मैं इस अद्भुत उपकरण का उपयोग करके "हॉट स्टार्ट" का अवसर देता हूं, क्योंकि मूल दस्तावेज का अध्ययन करने और मल्टी_इंडेक्स का उपयोग करने में टेम्पलेट्स के बारे में संकलक त्रुटियों को पार्स करने की कोशिश करने से शुरुआती लोगों के लिए बहुत निराशा होती है और कुछ अलग करने का प्रयास करता है। यह हमेशा की तरह निकलता है।

Source: https://habr.com/ru/post/In160009/


All Articles