डुप्लिकेट कहाँ से आते हैं और यह समस्या क्यों है?
\nकल्पना करें: आप API से डेटा ले रहे हैं, CSV फ़ाइल पार्स कर रहे हैं या यूज़र इनपुट प्रोसेस कर रहे हैं। 90% मामलों में आपको ऐसे arrays मिलेंगे जिनमें एक ही एलिमेंट कई बार आता है। यह सिर्फ एक सौंदर्य संबंधी समस्या नहीं है — डुप्लिकेट स्टैटिस्टिक्स को गलत कर सकते हैं, लिस्ट रेंडरिंग को तोड़ सकते हैं या एल्गोरिदम में अनंत लूप का कारण बन सकते हैं।
\nStack Overflow पर सवाल "JavaScript array से duplicate values हटाना" ने हज़ारों व्यूज़ बटोरे हैं, यह कोई संयोग नहीं है। यह एक बुनियादी लेकिन बेहद ज़रूरी ऑपरेशन है जो हर डेवलपर को आना चाहिए। आज हम अंदर की बात समझेंगे और देखेंगे कि अलग-अलग तरीके कैसे काम करते हैं — सबसे सरल से लेकर सबसे तेज़ तक।
\n\nतरीका #1: Set — सबसे सुंदर और तेज़
\nअगर आप किसी आधुनिक JavaScript डेवलपर से पूछें: "डुप्लिकेट कैसे हटाएं?", तो सबसे पहले वह Set का नाम लेगा। और यह सही है। लेकिन यह कैसे काम करता है?
const numbers = [1, 2, 2, 3, 4, 4, 5];\nconst uniqueNumbers = [...new Set(numbers)];\nconsole.log(uniqueNumbers); // [1, 2, 3, 4, 5]\n\nSet के अंदर क्या होता है?
\nSet ऑब्जेक्ट किसी भी प्रकार के यूनिक वैल्यू को स्टोर करता है। जब आप array को new Set(numbers) कंस्ट्रक्टर में पास करते हैं, तो JavaScript इंजन (V8, SpiderMonkey आदि) ये कदम उठाता है:
- \n
- Array पर Iteration: इंजन मूल array के हर एलिमेंट पर जाता है। \n
- यूनिकनेस की जाँच: हर एलिमेंट के लिए एक आंतरिक हैश फ़ंक्शन कॉल होता है। अगर एलिमेंट पहले से "बकेट" (bucket) में है, तो उसे अनदेखा किया जाता है। अगर नहीं है, तो जोड़ा जाता है। \n
- परिणाम तैयार करना:
[...set](spread) ऑपरेटर Set ऑब्जेक्ट को वापस array में बदल देता है। \n
एक महत्वपूर्ण बात: Set SameValueZero तुलना एल्गोरिदम का उपयोग करता है। इसका मतलब है कि NaN को NaN के बराबर माना जाता है (=== के विपरीत), और +0 और -0 को अलग-अलग माना जाता है। व्यवहार में इससे शायद ही कोई फर्क पड़ता है, लेकिन जानना अच्छा है।
Set की परफॉरमेंस
\nइस ऑपरेशन की जटिलता O(n) है। हर एलिमेंट की जाँच सिर्फ एक बार होती है। यह सभी तरीकों में सबसे अच्छा प्रदर्शन है। 10,000 एलिमेंट वाले arrays के लिए, Set और अन्य तरीकों के बीच का अंतर दसियों मिलीसेकंड का हो सकता है — हाई-लोड एप्लिकेशन के लिए यह महत्वपूर्ण है।
\n\nतरीका #2: filter + indexOf — क्लासिक तरीका
\nES6 (2015) से पहले, डेवलपर्स filter और indexOf के कॉम्बिनेशन का उपयोग करते थे। यह तरीका आज भी लीगेसी कोड में देखने को मिलता है:
const numbers = [1, 2, 2, 3, 4, 4, 5];\nconst uniqueNumbers = numbers.filter((value, index, self) => {\n return self.indexOf(value) === index;\n});\nconsole.log(uniqueNumbers); // [1, 2, 3, 4, 5]\n\nयह कैसे काम करता है?
\n- \n
filterarray के हर एलिमेंट पर जाता है। \n - हर एलिमेंट के लिए
indexOf(value)कॉल होता है, जो उस वैल्यू का पहला इंडेक्स लौटाता है। \n - अगर मौजूदा एलिमेंट का इंडेक्स पहले मिले इंडेक्स से मेल खाता है — तो यह पहली बार आया है, और एलिमेंट रखा जाता है। अगर नहीं मेल खाता — तो यह डुप्लिकेट है, और इसे हटा दिया जाता है। \n
कमियाँ
\nमुख्य समस्या परफॉरमेंस है। indexOf अंदर से लीनियर सर्च (हर कॉल के लिए O(n)) का उपयोग करता है। नतीजतन, N एलिमेंट वाले array के लिए जटिलता O(n²) हो जाती है। 1000 एलिमेंट पर यह सहनीय है, लेकिन 100,000 एलिमेंट पर यह धीमा हो जाता है।
एक और कमी: indexOf NaN के साथ सही से काम नहीं करता। अगर array में NaN है, तो indexOf(NaN) हमेशा -1 लौटाएगा, और एलिमेंट बार-बार जुड़ता रहेगा। NaN की जाँच के लिए अलग से Number.isNaN() का उपयोग करना होगा।