मैं वास्तव में मानक सेट कंटेनरों को अपने आप में ज्यादातर बेकार पाया जाता हूं और केवल सरणियों का उपयोग करना पसंद करता हूं, लेकिन मैं इसे एक अलग तरीके से करता हूं।
सेट किए गए चौराहों की गणना करने के लिए, मैं पहले सरणी के माध्यम से पुनरावृति करता हूं और एक ही बिट के साथ तत्वों को चिह्नित करता हूं। फिर मैं दूसरे सरणी के माध्यम से पुनरावृति करता हूं और चिह्नित तत्वों की तलाश करता हूं। वोइला, एक हैश टेबल की तुलना में दूर के काम और स्मृति के साथ रैखिक समय में प्रतिच्छेदन सेट, जैसे यूनियनों और मतभेद इस पद्धति का उपयोग करके लागू करने के लिए समान रूप से सरल हैं। यह मदद करता है कि मेरा कोडबेस उन्हें नकल करने के बजाय अनुक्रमण तत्वों के इर्द-गिर्द घूमता है (मैं तत्वों को इंडिकेट करता हूं, स्वयं तत्वों का डेटा नहीं) और शायद ही कभी किसी चीज़ को सॉर्ट करने की आवश्यकता होती है, लेकिन मैंने वर्षों में सेट डेटा संरचना का उपयोग नहीं किया है एक परिणाम।
मेरे पास कुछ दुष्ट बिट-सी-कोडिंग सी कोड है, जिसका उपयोग मैं तब भी करता हूं जब तत्व ऐसे उद्देश्यों के लिए कोई डेटा फ़ील्ड प्रदान नहीं करते हैं। इसमें ट्रैवर्स किए गए तत्वों को चिह्नित करने के उद्देश्य से सबसे महत्वपूर्ण बिट (जो मैं कभी उपयोग नहीं करता है) सेट करके तत्वों की स्मृति का उपयोग करना शामिल है। यह बहुत स्थूल है, ऐसा तब तक न करें जब तक कि आप वास्तव में पास-असेंबली स्तर पर काम नहीं कर रहे हैं, लेकिन सिर्फ यह उल्लेख करना चाहते हैं कि यह कैसे उन मामलों में भी लागू हो सकता है जब तत्व ट्रैवर्सल के लिए कुछ फ़ील्ड प्रदान नहीं करते हैं यदि आप गारंटी दे सकते हैं कि कुछ बिट्स का उपयोग कभी नहीं किया जाएगा। यह मेरी डिंकी i7 पर एक सेकंड से भी कम समय में 200 मिलियन एलिमेंट्स (बाउट 2.4 गीगा डेटा) के बीच एक सेट चौराहे की गणना कर सकता है। दो के बीच एक सेट चौराहा करने की कोशिश करेंstd::set
ही समय में प्रत्येक सौ मिलियन तत्वों वाले उदाहरणों के ; करीब भी नहीं आता है।
उसको छोड़कर...
हालाँकि, मैं यह भी कर सकता था कि प्रत्येक एलिमेंटो को किसी दूसरे वेक्टर में जोड़कर और जांचा जाए कि एलिमेंट पहले से मौजूद है या नहीं।
यह देखने के लिए कि क्या नए वेक्टर में एक तत्व पहले से मौजूद है, आम तौर पर एक रैखिक समय ऑपरेशन होने जा रहा है, जो सेट चौराहे को स्वयं एक द्विघात ऑपरेशन बना देगा (विस्फोटक मात्रा में काम जितना बड़ा इनपुट आकार)। मैं ऊपर दी गई तकनीक की सलाह देता हूं यदि आप केवल सादे पुराने वैक्टर या सरणियों का उपयोग करना चाहते हैं और इसे इस तरह से करते हैं कि आश्चर्यजनक रूप से तराजू होता है।
मूल रूप से: किस प्रकार के एल्गोरिदम को एक सेट की आवश्यकता होती है और किसी अन्य कंटेनर प्रकार के साथ नहीं किया जाना चाहिए?
अगर आप कंटेनर स्तर पर इसके बारे में बात कर रहे हैं तो कोई भी मेरी पक्षपाती राय नहीं पूछता है (जैसा कि विशेष रूप से सेट संचालन प्रदान करने के लिए विशेष रूप से लागू की गई डेटा संरचना में), लेकिन वैचारिक स्तर पर सेट लॉजिक के लिए बहुत कुछ है। उदाहरण के लिए, मान लें कि आप खेल की दुनिया में उन प्राणियों को ढूंढना चाहते हैं जो उड़ने और तैरने दोनों में सक्षम हैं, और आपके पास एक सेट में उड़ने वाले जीव हैं (चाहे आप वास्तव में सेट कंटेनर का उपयोग करें या नहीं) और दूसरे में तैरने वाले । उस मामले में, आप एक सेट चौराहा चाहते हैं। यदि आप ऐसे जीव चाहते हैं जो या तो उड़ सकते हैं या जादुई हैं, तो आप एक सेट यूनियन का उपयोग करते हैं। बेशक, आपको इसे लागू करने के लिए वास्तव में एक सेट कंटेनर की आवश्यकता नहीं है, और सबसे इष्टतम कार्यान्वयन को आमतौर पर एक कंटेनर की आवश्यकता नहीं होती है या नहीं चाहिए।
स्पर्शरेखा से दूर जा रहे हैं
ठीक है, मुझे इस सेट चौराहे के दृष्टिकोण के बारे में जिमीजैम से कुछ अच्छे सवाल मिले। यह थोड़े से विषय पर विचार कर रहा है, लेकिन अच्छी तरह से, मुझे यह देखने में दिलचस्पी है कि चौराहे पर सेट करने के लिए और अधिक लोग इस बुनियादी दखल देने वाले दृष्टिकोण का उपयोग करते हैं ताकि वे पूरी तरह से संतुलित द्विआधारी पेड़ों और हैश तालिकाओं जैसे संपूर्ण सहायक संरचनाओं का निर्माण न करें। जैसा कि मूलभूत आवश्यकता का उल्लेख किया गया है कि सूचियां उथली प्रतिलिपि तत्वों को शामिल करती हैं ताकि वे एक साझा तत्व को अनुक्रमित या इंगित कर सकें जो कि पहले चिह्नित सूची या सरणी के माध्यम से पारित होने के बाद या फिर दूसरे पर लेने के लिए "चिह्नित" हो सकता है। दूसरी सूची से गुजरें।
हालांकि, यह प्रदान किए गए तत्वों को छूने के बिना एक बहुस्तरीय संदर्भ में भी व्यावहारिक रूप से पूरा किया जा सकता है:
- दो समुच्चय तत्वों के लिए सूचक होते हैं।
- सूचकांकों की सीमा बहुत बड़ी नहीं है (कहते हैं [0, 2 ^ 26), अरबों या अधिक नहीं) और यथोचित घनीभूत हैं।
यह हमें सेट ऑपरेशन के उद्देश्य के लिए एक समानांतर सरणी (सिर्फ एक बिट प्रति तत्व) का उपयोग करने की अनुमति देता है। चित्र:
थ्रेड सिंक्रोनाइज़ेशन की जरूरत तब होती है जब पूल से एक समानांतर बिट ऐरे को प्राप्त किया जाता है और इसे वापस पूल में जारी किया जाता है (कार्यक्षेत्र से बाहर जाने पर अनुमानित रूप से किया जाता है)। सेट ऑपरेशन करने के लिए वास्तविक दो छोरों को किसी भी थ्रेड सिंक को शामिल करने की आवश्यकता नहीं है। हमें एक समानांतर बिट पूल का उपयोग करने की भी आवश्यकता नहीं है यदि थ्रेड को स्थानीय रूप से बिट्स आवंटित और मुक्त कर सकते हैं, लेकिन बिट पूल को कोडबेस में पैटर्न को सामान्य करने के लिए आसान हो सकता है जो इस तरह के डेटा प्रतिनिधित्व को फिट करते हैं जहां केंद्रीय तत्वों को अक्सर संदर्भित किया जाता है। सूचकांक द्वारा ताकि प्रत्येक थ्रेड को कुशल मेमोरी प्रबंधन के साथ परेशान न करना पड़े। मेरे क्षेत्र के लिए प्रमुख उदाहरण संस्था-घटक प्रणाली और अनुक्रमित मेष निरूपण हैं। दोनों को अक्सर सेट चौराहों की आवश्यकता होती है और सब कुछ संग्रहीत केन्द्रित करने की आवश्यकता होती है (ईसीएस और कोने, किनारों में घटक और इकाइयाँ,)
यदि सूचकांक घनी तरह से घेरे हुए नहीं हैं और कम बिखरे हुए हैं, तो यह अभी भी समानांतर बिट / बूलियन सरणी के एक उचित विरल कार्यान्वयन के साथ लागू होता है, जैसे कि जो केवल 512-बिट चंक्स में मेमोरी स्टोर करता है (64 बाइट्स अनियंत्रित नोड में 512 सन्निहित सूचकांकों का प्रतिनिधित्व करता है। ) और पूरी तरह से खाली सन्निहित ब्लॉकों को आवंटित करता है। संभावना है कि आप पहले से ही कुछ इस तरह का उपयोग कर रहे हैं यदि आपकी केंद्रीय डेटा संरचनाएं तत्वों द्वारा स्वयं पर कब्जा कर ली गई हैं।
... एक विरल बिटसेट के लिए समान विचार एक समानांतर बिट सरणी के रूप में सेवा करने के लिए। ये संरचनाएं खुद को अपरिवर्तनीयता की ओर ले जाती हैं क्योंकि यह आसान है कि एक नई अपरिवर्तनीय प्रतिलिपि बनाने के लिए चंकी ब्लॉकों को छिड़कना आसान नहीं है।
एक बहुत ही औसत मशीन पर इस दृष्टिकोण का उपयोग करके सैकड़ों से लाखों तत्वों के बीच फिर से सेट चौराहों को एक दूसरे के तहत किया जा सकता है, और यह एक ही धागे के भीतर है।
यह आधे से कम समय में भी किया जा सकता है यदि क्लाइंट को परिणामी चौराहे के लिए तत्वों की सूची की आवश्यकता नहीं है, जैसे कि यदि वे केवल दोनों सूचियों में पाए गए तत्वों के लिए कुछ तर्क लागू करना चाहते हैं, तो वे किस बिंदु पर पास कर सकते हैं एक फ़ंक्शन पॉइंटर या फ़िएक्टर या डेलीगेट या जो भी वापस बुलाया जाए वह उन तत्वों की श्रेणियों को संसाधित करता है जो प्रतिच्छेद करते हैं। इस आशय के लिए कुछ:
// 'func' receives a range of indices to
// process.
set_intersection(func):
{
parallel_bits = bit_pool.acquire()
// Mark the indices found in the first list.
for each index in list1:
parallel_bits[index] = 1
// Look for the first element in the second list
// that intersects.
first = -1
for each index in list2:
{
if parallel_bits[index] == 1:
{
first = index
break
}
}
// Look for elements that don't intersect in the second
// list to call func for each range of elements that do
// intersect.
for each index in list2 starting from first:
{
if parallel_bits[index] != 1:
{
func(first, index)
first = index
}
}
If first != list2.num-1:
func(first, list2.num)
}
... या इस प्रभाव के लिए कुछ। पहले आरेख में छद्मकोड का सबसे महंगा हिस्सा intersection.append(index)
दूसरे लूप में है, और यह std::vector
अग्रिम में छोटी सूची के आकार के लिए आरक्षित करने के लिए भी लागू होता है ।
क्या होगा अगर मैं सब कुछ डीप कॉपी करूँ?
खैर, कि बंद करो! यदि आपको सेट किए गए चौराहों को करने की आवश्यकता है, तो इसका मतलब है कि आप डेटा को नकल कर रहे हैं। संभावना है कि यहां तक कि आपके सबसे नन्हे ऑब्जेक्ट 32-बिट इंडेक्स से छोटे नहीं हैं। आपके तत्वों की एड्रेसिंग रेंज को 2 ^ 32 (2 ^ 32 तत्व, 2 ^ 32 बाइट्स) तक कम करना बहुत संभव है जब तक कि आपको वास्तव में ~ 4.3 बिलियन से अधिक तत्कालिक तत्वों की आवश्यकता न हो, जिस समय एक बिल्कुल अलग समाधान की आवश्यकता होती है ( और निश्चित रूप से सेट कंटेनरों को मेमोरी में उपयोग नहीं किया जा रहा है)।
कुंजी मिलान
ऐसे मामलों के बारे में जहां हमें ऑपरेशन सेट करने की आवश्यकता होती है जहां तत्व समान नहीं हैं लेकिन मिलान कुंजी हो सकती है? उस मामले में, ऊपर के रूप में एक ही विचार। हमें बस एक सूचकांक में प्रत्येक अद्वितीय कुंजी को मैप करने की आवश्यकता है। यदि कुंजी एक स्ट्रिंग है, उदाहरण के लिए, तो इंटर्न किए गए तार बस ऐसा कर सकते हैं। उन मामलों में ट्राई या हैश टेबल जैसी अच्छी डेटा संरचना को 32-बिट सूचकांकों के लिए स्ट्रिंग कुंजियों को मैप करने के लिए कहा जाता है, लेकिन परिणामी 32-बिट सूचकांकों पर सेट संचालन करने के लिए हमें ऐसी संरचनाओं की आवश्यकता नहीं है।
बहुत सारे सस्ते और सरल एल्गोरिथम सॉल्यूशन और डेटा स्ट्रक्चर्स इस तरह से खुलते हैं, जब हम इंडेक्स के साथ काम कर सकते हैं तत्वों को एक बहुत ही उचित रेंज में, न कि मशीन की पूरी एड्रेसिंग रेंज, और इसलिए यह अक्सर इसके लायक होने से अधिक है प्रत्येक अद्वितीय कुंजी के लिए एक अद्वितीय सूचकांक प्राप्त करने में सक्षम।
आई लव इंडिक्स!
मुझे पिज्ज़ा और बीयर जितना ही पसंद है। जब मैं अपने 20 के दशक में था, मैं वास्तव में सी ++ में मिला और पूरी तरह से मानक-अनुरूप डेटा संरचनाओं को डिजाइन करना शुरू कर दिया (जिसमें संकलन-समय पर एक सीमा ctor से भराव ctor को हटाने के लिए शामिल चालें शामिल हैं)। रेट्रोस्पेक्ट में जो समय की एक बड़ी बर्बादी थी।
यदि आप अपने डेटाबेस को केंद्र में एरे में तत्वों को संग्रहीत करने और उन्हें अनुक्रमणित करने के बजाय मशीन के संपूर्ण पता सीमा में संभावित रूप से संग्रहीत करने और उन्हें अनुक्रमणित करने के लिए घूमते हैं, तो आप बस एल्गोरिथम और डेटा संभावनाओं की दुनिया की खोज कर सकते हैं। कंटेनर और एल्गोरिदम को डिजाइन करना जो सादे पुराने int
या घूमते हैंint32_t
। और मैंने पाया कि अंतिम परिणाम बहुत अधिक कुशल और बनाए रखने में आसान है जहां मैं लगातार एक डेटा संरचना से दूसरे में तत्वों को दूसरे में स्थानांतरित नहीं कर रहा था।
कुछ उदाहरण ऐसे मामलों का उपयोग करते हैं, जब आप यह मान सकते हैं कि किसी भी विशिष्ट मूल्य का T
एक अद्वितीय सूचकांक है और एक केंद्रीय सरणी में रहने वाले उदाहरण होंगे:
मल्टीथ्रेडेड रेडिक्स प्रकार जो सूचकांकों के लिए अहस्ताक्षरित पूर्णांक के साथ अच्छी तरह से काम करते हैं । मेरे पास वास्तव में एक मल्टीथ्रेडेड रेडिक्स सॉर्ट है जो कि इंटेल के अपने समानांतर सॉर्ट के रूप में सौ मिलियन तत्वों को सॉर्ट करने में लगभग 1/10 वां समय लेता है, और इंटेल std::sort
ऐसे बड़े इनपुट की तुलना में पहले से 4 गुना तेज है । बेशक इंटेल की तुलना अधिक लचीली है क्योंकि यह तुलना-आधारित प्रकार है और चीजों को क्रमबद्ध रूप से सॉर्ट कर सकता है, इसलिए यह सेब की तुलना संतरे से कर रहा है। लेकिन यहाँ मुझे अक्सर केवल संतरों की आवश्यकता होती है, जैसे मैं कैश-फ्रेंडली मेमोरी एक्सेस पैटर्न प्राप्त करने के लिए एक रेडिक्स सॉर्ट पास कर सकता हूं या डुप्लिकेट को जल्दी से फ़िल्टर कर सकता हूं।
लिंक्ड नोड्स प्रति ढेर आवंटन के बिना लिंक किए गए सूचियों, पेड़ों, रेखांकन, अलग-अलग चेनिंग हैश टेबल आदि जैसी संरचनाओं का निर्माण करने की क्षमता । हम तत्वों के समानांतर थोक में नोड्स आवंटित कर सकते हैं, और उन्हें सूचकांकों के साथ जोड़ सकते हैं। नोड्स खुद अगले नोड के लिए 32-बिट इंडेक्स बन जाते हैं और एक बड़े सरणी में संग्रहीत होते हैं, जैसे:
समानांतर प्रसंस्करण के लिए अनुकूल है। अक्सर जुड़ी संरचनाएं समानांतर प्रसंस्करण के लिए इतनी अनुकूल नहीं होती हैं, क्योंकि यह पेड़ में समानांतरवाद को प्राप्त करने के लिए बहुत कम से कम अजीब है या लिंक किए गए सूची ट्रैवर्सल के विपरीत, कहने के लिए, बस एक सरणी के माध्यम से लूप के लिए समानांतर कर रहा है। सूचकांक / केंद्रीय सरणी प्रतिनिधित्व के साथ, हम हमेशा उस केंद्रीय सरणी पर जा सकते हैं और चंकी समानांतर छोरों में सब कुछ संसाधित कर सकते हैं। हमारे पास हमेशा सभी तत्वों का वह केंद्रीय सरणी होता है जिसे हम इस तरह से संसाधित कर सकते हैं, भले ही हम केवल कुछ प्रक्रिया करना चाहते हों (जिस बिंदु पर आप केंद्रीय सरणी के माध्यम से कैश-फ्रेंडली एक्सेस के लिए एक रेडिक्स-सॉर्ट की गई सूची द्वारा अनुक्रमित तत्वों को संसाधित कर सकते हैं)।
निरंतर-समय में मक्खी पर प्रत्येक तत्व के लिए डेटा को जोड़ सकते हैं । ऊपर बिट्स के समानांतर सरणी के मामले के साथ, हम आसानी से और बेहद सस्ते समानांतर डेटा को तत्वों के लिए कह सकते हैं, जैसे कि, अस्थायी प्रसंस्करण। इसमें अस्थायी डेटा से परे मामलों का उपयोग किया गया है। उदाहरण के लिए, एक मेष प्रणाली उपयोगकर्ताओं को एक जाल के रूप में कई यूवी नक्शे संलग्न करने की अनुमति देना चाह सकती है। ऐसे मामले में, हम केवल हार्ड-कोड नहीं कर सकते हैं कि एओएस दृष्टिकोण का उपयोग करके प्रत्येक एकल शीर्ष और चेहरे में कितने यूवी नक्शे होंगे। हमें मक्खी पर इस तरह के डेटा को जोड़ने में सक्षम होने की आवश्यकता है, और समानांतर सरणियां वहां काम कर रही हैं और किसी भी प्रकार ओ परिष्कृत सहकारी कंटेनर, यहां तक कि हैश तालिकाओं की तुलना में बहुत सस्ता है।
बेशक समानांतर सरणियों को एक दूसरे के साथ समानांतर सरणियों को रखने की उनकी त्रुटि-प्रवण प्रकृति के कारण पर फेंक दिया जाता है। जब भी हम "मूल" सरणी से सूचकांक 7 पर एक तत्व निकालते हैं, उदाहरण के लिए, हम इसी तरह "बच्चों" के लिए एक ही काम करते हैं। हालाँकि, इस अवधारणा को सामान्य-उद्देश्य वाले कंटेनर में सामान्य करने के लिए अधिकांश भाषाओं में यह काफी आसान है, ताकि एक दूसरे के साथ समानांतर सरणियों को रखने के लिए मुश्किल तर्क पूरे कोडबेस में एक ही स्थान पर मौजूद हों, और ऐसा समानांतर सरणी कंटेनर में हो सकता है अन्य आवेषण पर पुनः प्राप्त करने के लिए सरणी में सन्निहित खाली स्थानों के लिए बहुत सारी स्मृति को बर्बाद करने से बचने के लिए ऊपर विरल सरणी कार्यान्वयन का उपयोग करें।
अधिक विस्तार: विरल बिट्स ट्री
ठीक है, मुझे कुछ और विस्तृत करने का अनुरोध मिला जो मुझे लगता है कि व्यंग्यात्मक था, लेकिन मैं ऐसा करने वाला हूं, क्योंकि यह बहुत मजेदार है! यदि लोग इस विचार को पूरे नए स्तरों पर ले जाना चाहते हैं, तो एन + एम तत्वों के माध्यम से भी रैखिक रूप से लूपिंग के बिना सेट चौराहों का प्रदर्शन करना संभव है। यह मेरी अंतिम डेटा संरचना है जो मैं उम्र और मूल रूप से मॉडल के लिए उपयोग कर रहा हूं set<int>
:
इसका कारण यह है कि दोनों सूचियों में प्रत्येक तत्व का निरीक्षण किए बिना सेट चौराहों का प्रदर्शन किया जा सकता है क्योंकि पदानुक्रम की जड़ में एक एकल सेट बिट यह संकेत दे सकता है कि, सेट में एक लाख सन्निहित तत्वों का कब्जा है। केवल एक बिट का निरीक्षण करने से, हम जान सकते हैं कि रेंज में N सूचकांक, [first,first+N)
सेट में हैं, जहां N एक बहुत बड़ी संख्या हो सकती है।
मैं वास्तव में एक लूप ऑप्टिमाइज़र के रूप में उपयोग करता हूं जब कब्जा किए गए सूचकांकों का पता लगाता हूं, क्योंकि मान लीजिए कि सेट में 8 मिलियन सूचकांक हैं। ठीक है, आम तौर पर हमें उस स्थिति में 8 मिलियन पूर्णांकों को मेमोरी में एक्सेस करना होगा। इस एक के साथ, यह संभावित रूप से बस कुछ बिट्स का निरीक्षण कर सकता है और कब्जे वाले सूचकांकों की सूचकांक सीमाओं के साथ आ सकता है। इसके अलावा, इसके साथ आने वाले सूचकांकों की सीमाएं क्रमबद्ध क्रम में होती हैं, जो मूल तत्व डेटा तक पहुंचने के लिए उपयोग किए जाने वाले सूचकांकों के एक अनसुने सरणी के माध्यम से पुनरावृत्ति करते हुए, बहुत कैश-फ्रेंडली अनुक्रमिक पहुंच के लिए बनाता है। बेशक यह तकनीक अत्यंत विरल मामलों के लिए बदतर है, सबसे खराब स्थिति के साथ हर एक सूचकांक एक समान संख्या (या हर एक विषम) होने की स्थिति में है, इस मामले में कोई भी संबंधित क्षेत्र नहीं हैं। लेकिन मेरे उपयोग के मामलों में कम से कम,