किस तरह के एल्गोरिथ्म के लिए एक सेट की आवश्यकता होती है?


10

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

मूल रूप से: किस प्रकार के एल्गोरिदम को एक सेट की आवश्यकता होती है और किसी अन्य कंटेनर प्रकार के साथ नहीं किया जाना चाहिए?


2
जब आप "सेट" शब्द का उपयोग करते हैं तो क्या आप इसके बारे में अधिक विशिष्ट हो सकते हैं? क्या आप C ++ सेट की
रॉबर्ट हार्वे

हां, वास्तव में, "सेट" की परिभाषा अधिकांश भाषाओं में काफी समान है: एक कंटेनर जो केवल अद्वितीय तत्वों को स्वीकार करता है।
फ्लिएला

6
"प्रत्येक तत्व को दूसरे वेक्टर में जोड़ना और जांचना कि क्या तत्व पहले से मौजूद है" - यह सिर्फ एक सेट को लागू करना है। तो आप पूछ रहे हैं कि जब आप अपने आप को हाथ से लिख सकते हैं तो अंतर्निहित सुविधा का उपयोग क्यों करें?
जैक्सबी

जवाबों:


8

आप विशेष रूप से सेट के बारे में पूछ रहे हैं, लेकिन मुझे लगता है कि आपका प्रश्न एक बड़ी अवधारणा के बारे में है: अमूर्त। आप पूरी तरह से सही हैं कि आप ऐसा करने के लिए एक वेक्टर का उपयोग कर सकते हैं (यदि आप जावा का उपयोग कर रहे हैं, तो इसके बजाय ArrayList का उपयोग करें।) लेकिन वहां क्यों रुकें? आपको वेक्टर की क्या आवश्यकता है? आप यह सब सरणियों के साथ कर सकते हैं।

जब आपको किसी आइटम को सरणी में जोड़ने की आवश्यकता होती है, तो आप बस हर तत्व पर लूप कर सकते हैं और यदि यह नहीं है, तो आप इसे अंत में जोड़ते हैं। लेकिन, वास्तव में, आपको पहले यह जांचने की आवश्यकता है कि क्या सरणी में कमरा है या नहीं। यदि आपको कोई नया सरणी बनाने की आवश्यकता नहीं है, तो वह बड़ा है और पुराने सरणी से नए सरणी में सभी मौजूदा तत्वों को कॉपी करें और फिर आप नए तत्व को जोड़ सकते हैं। बेशक, आपको नए को इंगित करने के लिए पुराने सरणी के हर संदर्भ को अपडेट करने की भी आवश्यकता है। हो गया सब? महान! अब हम फिर से क्या हासिल करने की कोशिश कर रहे थे?

या, इसके बजाय आप सेट उदाहरण का उपयोग कर सकते हैं और बस कॉल कर सकते हैं add()। कारण है कि सेट मौजूद है कि वे एक अमूर्त है कि आम समस्याओं के बहुत सारे के लिए उपयोगी है। उदाहरण के लिए, मान लें कि आप आइटम ट्रैक करना चाहते हैं और एक नया जोड़ा जाने पर प्रतिक्रिया करना चाहते हैं। आप add()एक सेट पर कॉल करते हैं और यह रिटर्न सेट पर आधारित होता है trueया falseनहीं। आप लिख सकते हैं कि सभी हाथ से आदिम का उपयोग कर रहे हैं लेकिन क्यों?

वास्तव में ऐसा मामला हो सकता है जहां आपके पास एक सूची है और आप डुप्लिकेट को निकालना चाहते हैं। आपके द्वारा प्रस्तावित एल्गोरिथ्म बहुत ही मूल रूप से सबसे धीमा तरीका है जिसे आप कर सकते हैं। वहाँ आम जल्दी तरीके के एक जोड़े हैं: उन्हें बाल्टी या उन्हें छँटाई। या, आप उन्हें एक सेट में जोड़ सकते हैं जो उन एल्गोरिदम में से एक को लागू करता है।

अपने करियर / शिक्षा के शुरुआती दौर में इन एल्गोरिदम को बनाने और उन्हें समझने पर ध्यान केंद्रित किया गया है और ऐसा करना महत्वपूर्ण है। लेकिन ऐसा नहीं है कि पेशेवर डेवलपर्स सामान्य आधार पर करते हैं। वे बहुत अधिक दिलचस्प चीजों का निर्माण करने के लिए इन दृष्टिकोणों का उपयोग करते हैं और पूर्व-निर्मित और विश्वसनीय कार्यान्वयन का उपयोग करके समय की नावों को बचाता है।


23

मुझे लगता है कि इस्तेमाल की जाने वाली भाषा के आधार पर प्रदर्शन में अंतर हो सकता है। लेकिन क्या इसके अलावा एक सेट का उपयोग करने का कोई कारण है?

अरे हाँ, (लेकिन यह प्रदर्शन नहीं है।)

जब आप एक का उपयोग कर सकते हैं तो एक सेट का उपयोग करें क्योंकि इसका उपयोग नहीं करने का मतलब है कि आपको अतिरिक्त कोड लिखना होगा। सेट का उपयोग करने से आपके पढ़ने में आसानी होती है। विशिष्टता तर्क के लिए सभी परीक्षण कहीं और छिपाए जाते हैं जहां आपको इसके बारे में सोचने की ज़रूरत नहीं है। यह एक ऐसी जगह पर है जो पहले से ही परीक्षण किया गया है और आप भरोसा कर सकते हैं कि यह काम करता है।

ऐसा करने के लिए अपना खुद का कोड लिखें और आपको इसके बारे में चिंता करना होगा। Bleh। कौन क्या करना चाहता है?

मूल रूप से: किस प्रकार के एल्गोरिदम को एक सेट की आवश्यकता होती है और किसी अन्य कंटेनर प्रकार के साथ नहीं किया जाना चाहिए?

कोई एल्गोरिथ्म नहीं है कि "किसी अन्य कंटेनर प्रकार के साथ नहीं किया जाना चाहिए"। बस एल्गोरिदम हैं जो सेट का लाभ उठा सकते हैं। यह अच्छा है जब आपको अतिरिक्त कोड नहीं लिखना है।

अब इस संबंध में विशेष रूप से सेट के बारे में कुछ भी विशेष नहीं है। आपको हमेशा उस संग्रह का उपयोग करना चाहिए जो आपकी आवश्यकताओं के लिए सबसे उपयुक्त हो। जावा में मुझे यह चित्र उस निर्णय लेने में मददगार पाया गया है। आप देखेंगे कि इसमें तीन अलग-अलग प्रकार के सेट हैं।

यहां छवि विवरण दर्ज करें

और जैसा कि @germi सही इंगित करता है, यदि आप नौकरी के लिए सही संग्रह का उपयोग करते हैं तो आपका कोड दूसरों के लिए पढ़ना आसान हो जाता है।


6
आपने इसका उल्लेख पहले ही कर दिया था, लेकिन एक सेट का उपयोग करना भी अन्य लोगों के लिए कोड के बारे में तर्क देना आसान बनाता है; उन्हें यह देखने की ज़रूरत नहीं है कि यह जानने के लिए कैसे आबादी है कि इसमें केवल अद्वितीय आइटम हैं।
जर्मि

14

हालाँकि, मैं यह भी कर सकता था कि प्रत्येक एलिमेंटो को किसी दूसरे वेक्टर में जोड़कर और जांचा जाए कि एलिमेंट पहले से मौजूद है या नहीं।

यदि आप ऐसा करते हैं, तो आप वेक्टर डेटास्ट्रक्चर के शीर्ष पर एक सेट के शब्दार्थ को लागू कर रहे हैं । आप अतिरिक्त कोड लिख रहे हैं (जिसमें त्रुटियां हो सकती हैं), और यदि आपके पास बहुत सारी प्रविष्टियाँ हैं, तो परिणाम बेहद धीमा होगा।

आप मौजूदा, परीक्षणित, कुशल सेट कार्यान्वयन का उपयोग करके ऐसा क्यों करना चाहेंगे?


6

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

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

सेट कैसे लागू किए जाते हैं और विशिष्टता की गारंटी कैसे दी जाती है, यह एक और मामला है। एक उम्मीद है कि सेट लॉजिक के लिए एक उपयुक्त कार्यान्वयन मिल सकता है जो दिए गए डुप्लिकेट को समाप्त करता है जो कि लॉजिक के लिए इतना मौलिक है, लेकिन फिर भी यदि आप खुद को लागू करते हैं, तो विशिष्टता की गारंटी एक सेट में एक आइटम के सम्मिलन के लिए आंतरिक है। यदि तत्व पहले से मौजूद है तो आपको "जाँच" नहीं करनी चाहिए।


"जाँचता है कि क्या यह पहले से मौजूद है" अक्सर कटौती के लिए आवश्यक है। अक्सर ऑब्जेक्ट डेटा से बनाए जाते हैं। और आप समान डेटा के लिए केवल एक ऑब्जेक्ट चाहते हैं, उसी डेटा से ऑब्जेक्ट बनाने वाले किसी व्यक्ति द्वारा पुन: उपयोग किया जाए। इसलिए आप एक नई वस्तु बनाएं, यह जांचें कि क्या यह सेट में है, यदि यह वहां है तो आप सेट से ऑब्जेक्ट लेते हैं, अन्यथा आप अपना ऑब्जेक्ट डालते हैं। यदि आपने अभी ऑब्जेक्ट डाला है, तो आपके पास अभी भी बहुत सी समान वस्तुएं होंगी।
gnasher729

1
@ gnasher729 सेट के कार्यान्वयनकर्ता की जिम्मेदारी में अस्तित्व के लिए जाँच शामिल है, लेकिन सेट का एक उपयोगकर्ताfor 1..100: set.insert(10) और अभी भी जान सकता है कि सेट में केवल 10 है
Caleth

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

4

प्रदर्शन विशेषताओं के अलावा (जो बहुत महत्वपूर्ण हैं, और इतनी आसानी से खारिज नहीं किया जाना चाहिए), सेट एक सार संग्रह के रूप में बहुत महत्वपूर्ण हैं।

क्या आप ऐरे के साथ सेट व्यवहार (प्रदर्शन की अनदेखी) का अनुकरण कर सकते हैं? हाँ बिल्कुल! हर बार जब आप सम्मिलित करते हैं, तो आप जांच सकते हैं कि क्या तत्व पहले से ही सरणी में है, और उसके बाद ही तत्व जोड़ें यदि यह पहले से ही नहीं मिला है। लेकिन यह कुछ ऐसा है जिसके बारे में आपको सचेत रूप से अवगत होना है, और हर बार जब आप अपने ऐरे-पूसीडो-सेट में सम्मिलित होते हैं, तो याद रखें। ओह क्या है, आपने एक बार सीधे डाला, बिना डुप्लिकेट के पहले जाँच के बिना? वेल्ड, आपके सरणी ने अपने अपरिवर्तनीय को तोड़ दिया है (यह सभी तत्व अद्वितीय हैं, और समकक्ष रूप से, कि कोई डुप्लिकेट मौजूद नहीं है)।

तो आप उसके आसपास जाने के लिए क्या करेंगे? आप एक नया डेटा प्रकार बनाएंगे, इसे कॉल (कहें PsuedoSet) करेंगे, जो एक आंतरिक एरे को लपेटता है, और एक insertऑपरेशन को सार्वजनिक रूप से उजागर करता है , जो तत्वों की विशिष्टता को लागू करेगा। चूंकि लिपटे सरणी केवल इस सार्वजनिक insertएपीआई के माध्यम से सुलभ हैं , आप गारंटी देते हैं कि डुप्लिकेट कभी भी नहीं आ सकते हैं। अब containsचेक के प्रदर्शन में सुधार करने के लिए कुछ हैशिंग जोड़ें और बाद में या बाद में आपको एहसास होगा कि आपने एक पूर्ण-लागू किया है Set

मैं भी एक बयान के साथ जवाब और सवाल का पालन करेंगे:

मेरे पहले प्रोग्रामिंग पाठ्यक्रमों में मुझे बताया गया था कि जब भी मुझे किसी चीज़ के कई ऑर्डर किए गए तत्वों को स्टोर करने की आवश्यकता होती है, तो मुझे एक ऐरे का उपयोग करना चाहिए। उदाहरण के लिए: सहकर्मियों के नामों के संग्रह को संग्रहीत करने के लिए। हालाँकि, मैं यह भी कर सकता था कि कच्ची मेमोरी को आबंटित करके, स्टार्ट पॉइंटर + द्वारा दिए गए मेमोरी एड्रेस की वैल्यू सेट करके कुछ ऑफसेट किया जाए।

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

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


3

सभी प्रकार के सेट आधारित एल्गोरिदम हैं, खासकर जहां आपको सेट के चौराहों और यूनियनों को प्रदर्शन करने की आवश्यकता होती है और परिणाम एक सेट होता है।

सेट आधारित एल्गोरिदम का उपयोग विभिन्न पथों में एल्गोरिदम इत्यादि में भारी रूप से किया जाता है।

सेट सिद्धांत पर एक प्राइमर के लिए इस लिंक को देखें: http://people.umass.edu/partee/NZ_2006/Set%20Theory%20Basics.pdf

यदि आपको सेट शब्दार्थ की आवश्यकता है, तो एक सेट का उपयोग करें। यह नकली डुप्लिकेट के कारण कीड़े से बचने के लिए जा रहा है क्योंकि आप कुछ स्तर पर वेक्टर / सूची को prune करना भूल गए हैं, और यह आपके वेक्टर / सूची को लगातार छांट कर आप की तुलना में तेज हो सकता है।


1

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

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

मेरे पास कुछ दुष्ट बिट-सी-कोडिंग सी कोड है, जिसका उपयोग मैं तब भी करता हूं जब तत्व ऐसे उद्देश्यों के लिए कोई डेटा फ़ील्ड प्रदान नहीं करते हैं। इसमें ट्रैवर्स किए गए तत्वों को चिह्नित करने के उद्देश्य से सबसे महत्वपूर्ण बिट (जो मैं कभी उपयोग नहीं करता है) सेट करके तत्वों की स्मृति का उपयोग करना शामिल है। यह बहुत स्थूल है, ऐसा तब तक न करें जब तक कि आप वास्तव में पास-असेंबली स्तर पर काम नहीं कर रहे हैं, लेकिन सिर्फ यह उल्लेख करना चाहते हैं कि यह कैसे उन मामलों में भी लागू हो सकता है जब तत्व ट्रैवर्सल के लिए कुछ फ़ील्ड प्रदान नहीं करते हैं यदि आप गारंटी दे सकते हैं कि कुछ बिट्स का उपयोग कभी नहीं किया जाएगा। यह मेरी डिंकी i7 पर एक सेकंड से भी कम समय में 200 मिलियन एलिमेंट्स (बाउट 2.4 गीगा डेटा) के बीच एक सेट चौराहे की गणना कर सकता है। दो के बीच एक सेट चौराहा करने की कोशिश करेंstd::set ही समय में प्रत्येक सौ मिलियन तत्वों वाले उदाहरणों के ; करीब भी नहीं आता है।

उसको छोड़कर...

हालाँकि, मैं यह भी कर सकता था कि प्रत्येक एलिमेंटो को किसी दूसरे वेक्टर में जोड़कर और जांचा जाए कि एलिमेंट पहले से मौजूद है या नहीं।

यह देखने के लिए कि क्या नए वेक्टर में एक तत्व पहले से मौजूद है, आम तौर पर एक रैखिक समय ऑपरेशन होने जा रहा है, जो सेट चौराहे को स्वयं एक द्विघात ऑपरेशन बना देगा (विस्फोटक मात्रा में काम जितना बड़ा इनपुट आकार)। मैं ऊपर दी गई तकनीक की सलाह देता हूं यदि आप केवल सादे पुराने वैक्टर या सरणियों का उपयोग करना चाहते हैं और इसे इस तरह से करते हैं कि आश्चर्यजनक रूप से तराजू होता है।

मूल रूप से: किस प्रकार के एल्गोरिदम को एक सेट की आवश्यकता होती है और किसी अन्य कंटेनर प्रकार के साथ नहीं किया जाना चाहिए?

अगर आप कंटेनर स्तर पर इसके बारे में बात कर रहे हैं तो कोई भी मेरी पक्षपाती राय नहीं पूछता है (जैसा कि विशेष रूप से सेट संचालन प्रदान करने के लिए विशेष रूप से लागू की गई डेटा संरचना में), लेकिन वैचारिक स्तर पर सेट लॉजिक के लिए बहुत कुछ है। उदाहरण के लिए, मान लें कि आप खेल की दुनिया में उन प्राणियों को ढूंढना चाहते हैं जो उड़ने और तैरने दोनों में सक्षम हैं, और आपके पास एक सेट में उड़ने वाले जीव हैं (चाहे आप वास्तव में सेट कंटेनर का उपयोग करें या नहीं) और दूसरे में तैरने वाले । उस मामले में, आप एक सेट चौराहा चाहते हैं। यदि आप ऐसे जीव चाहते हैं जो या तो उड़ सकते हैं या जादुई हैं, तो आप एक सेट यूनियन का उपयोग करते हैं। बेशक, आपको इसे लागू करने के लिए वास्तव में एक सेट कंटेनर की आवश्यकता नहीं है, और सबसे इष्टतम कार्यान्वयन को आमतौर पर एक कंटेनर की आवश्यकता नहीं होती है या नहीं चाहिए।

स्पर्शरेखा से दूर जा रहे हैं

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

हालांकि, यह प्रदान किए गए तत्वों को छूने के बिना एक बहुस्तरीय संदर्भ में भी व्यावहारिक रूप से पूरा किया जा सकता है:

  1. दो समुच्चय तत्वों के लिए सूचक होते हैं।
  2. सूचकांकों की सीमा बहुत बड़ी नहीं है (कहते हैं [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 मिलियन पूर्णांकों को मेमोरी में एक्सेस करना होगा। इस एक के साथ, यह संभावित रूप से बस कुछ बिट्स का निरीक्षण कर सकता है और कब्जे वाले सूचकांकों की सूचकांक सीमाओं के साथ आ सकता है। इसके अलावा, इसके साथ आने वाले सूचकांकों की सीमाएं क्रमबद्ध क्रम में होती हैं, जो मूल तत्व डेटा तक पहुंचने के लिए उपयोग किए जाने वाले सूचकांकों के एक अनसुने सरणी के माध्यम से पुनरावृत्ति करते हुए, बहुत कैश-फ्रेंडली अनुक्रमिक पहुंच के लिए बनाता है। बेशक यह तकनीक अत्यंत विरल मामलों के लिए बदतर है, सबसे खराब स्थिति के साथ हर एक सूचकांक एक समान संख्या (या हर एक विषम) होने की स्थिति में है, इस मामले में कोई भी संबंधित क्षेत्र नहीं हैं। लेकिन मेरे उपयोग के मामलों में कम से कम,


2
"सेट चौराहों की गणना करने के लिए, मैं पहले सरणी के माध्यम से पुनरावृत्त करता हूं और तत्वों को एक बिट के साथ चिह्नित करता हूं। फिर मैं दूसरे सरणी के माध्यम से पुनरावृति करता हूं और चिह्नित तत्वों की तलाश करता हूं।" आप उन्हें चिह्नित करते हैं जहां, दूसरे सरणी पर?
जिम्मीजैम

1
ओह, मैं देख रहा हूं, आप डेटा को प्रत्येक मूल्य का प्रतिनिधित्व करने वाली एकल वस्तु 'इंटर्निंग' कर रहे हैं। यह सेट के लिए उपयोग के मामलों के सबसेट के लिए एक दिलचस्प तकनीक है। मुझे कोई कारण नहीं है कि इस दृष्टिकोण को अपने स्वयं के सेट क्लास पर एक ऑपरेशन के रूप में लागू करने के लिए क्यों नहीं।
जिम्मीजम्स

2
"यह एक घुसपैठ समाधान है जो कुछ मामलों में इनकैप्सुलेशन का उल्लंघन करता है ..." एक बार जब मुझे पता चला कि आपका क्या मतलब है, तो मेरे साथ ऐसा हुआ लेकिन फिर मुझे लगता है कि इसकी आवश्यकता नहीं है। यदि आपके पास एक वर्ग है जो इस व्यवहार को प्रबंधित करता है, तो सूचकांक ऑब्जेक्ट सभी तत्व डेटा से स्वतंत्र हो सकते हैं और आपके संग्रह प्रकार के सभी उदाहरणों में साझा किए जा सकते हैं। यानी डेटा का एक मास्टर सेट होगा और फिर प्रत्येक उदाहरण मास्टर सेट पर वापस आ जाएगा। मल्टी-थ्रेडिंग को और अधिक जटिलता की आवश्यकता होगी लेकिन मुझे लगता है कि अगर प्रबंधनीय होगा।
जिमीजैम

1
ऐसा लगता है कि यह डेटाबेस समाधान में संभावित रूप से उपयोगी होगा, लेकिन मुझे नहीं पता कि क्या इस तरह से कोई क्रियान्वयन होता है। इसे यहाँ डालने के लिए धन्यवाद। आपने मेरा काम करने का मन बना लिया।
जिमीजैम

1
क्या आप थोड़ा और विस्तृत कर सकते हैं? ;) मैं इसकी जाँच करूँगा जब मेरे पास कुछ (बहुत) समय होगा।
जिमीजैम

-1

यह जांचने के लिए कि क्या n तत्वों वाले सेट में एक और तत्व X है जिसमें आमतौर पर निरंतर समय लगता है। यह जाँचने के लिए कि n तत्वों वाले सरणी में एक और तत्व X है जो आमतौर पर O (n) समय लेता है। यह बुरा है, लेकिन यदि आप डुप्लिकेट को n आइटम से हटाना चाहते हैं, तो अचानक O (n) 2 के बजाय समय में O (n) लेता है; 100,000 आइटम आपके कंप्यूटर को उसके घुटनों पर लाएंगे।

और तुम और कारण पूछ रहे हो? "शूटिंग के अलावा, क्या आपने शाम का आनंद लिया, श्रीमती लिंकन?"


2
मुझे लगता है कि आप उस पर फिर से पढ़ना चाहते हैं। O (n) समय के बजाय O (n) समय लेना आमतौर पर एक अच्छी बात मानी जाती है।
जिमीजैम

शायद इसे पढ़ते हुए आप सिर के बल खड़े हो गए? ओपी ने पूछा "क्यों नहीं बस एक सरणी ले लो"।
gnasher729

2
O (n²) से O (n) तक क्यों जा रहा है, एक 'कंप्यूटर टू इट्स नाइट्स' लाने जा रहा है? मुझे अपनी कक्षा में वह याद आ गया होगा।
जिमीजैम
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.