स्पॉइलर चेतावनी: इस लेख में सामान्य रूप से अनाज अभ्यास के बारे में स्पॉइलर हैं, और खास तौर पर Bash ट्रैक वाले अनाज अभ्यास के। अगर आपने इसे अभी तक खुद पूरा नहीं किया है, और आप नहीं चाहते कि आपको कुछ हल दिखाए जाएँ, तो इसे पूरा करने के बाद वापस आइए!
यह एक नई कंपनी में आपका पहला दिन है। आप सारा कागज़ी काम निपटा चुके हैं, टीम से मिल चुके हैं, और अब आखिरकार समय आ गया है कि आप बैठकर उस कोड को पढ़ना शुरू करें जिस पर आप काम करने वाले हैं। आप अलग-अलग फंक्शन, क्लास और मॉड्यूल पढ़ना शुरू करते हैं। पढ़ते-पढ़ते आप पाते हैं कि आप उलझन में आँखें सिकोड़कर स्क्रीन को देखने लगे हैं। आप पढ़ते रहते हैं, और आपके मुँह से एक शब्द निकलता है, मुश्किल से बोला हुआ, लगभग साँस में घुला हुआ: "क्याआआआआ..."1 जितना आगे बढ़ते हैं, उतना ही यह होता रहता है, और आप और ज़्यादा उलझते जाते हैं, थोड़ा गुस्सा भी आने लगता है।
इस कोड में क्या हो रहा है?
जब भी किसी कोड पर एक से ज़्यादा लोग काम करते हैं, तो चीज़ों को संभाले रखने के लिए जितनी सावधानी और सोच-समझ चाहिए, वह बहुत बढ़ जाती है। अब बात यह नहीं रही कि कॉन्सेप्ट आपके दिमाग में रहे और कोड को बस उसे पूरा करना हो। अब कॉन्सेप्ट को कोड के अंदर रहना पड़ता है, जहाँ साथ काम करने वाले सभी लोग उसे देख सकें और ज़रूरत पड़ने पर बदल सकें।
कोई चीज़ आप कैसे लागू करते हैं, इससे अंतिम उपयोगकर्ता को कोई खास फ़र्क नहीं पड़ता, लेकिन हर उस इंजीनियर के लिए यह बहुत कुछ कहता होना चाहिए जो किसी भी मोड़ पर आपके डिज़ाइन को छूता है। एक ही काम करने के लिए अक्सर कई तरीके होते हैं, और लग सकता है कि इनमें से कोई भी तरीका काम पूरा करने के लिए काफी है। लेकिन मेरा मानना है कि आपका हर एक फैसला किसी वजह से होना चाहिए (चाहे वह छोटा फैसला हो और वजह भी छोटी), और उस वजह से किसी उद्देश्य या ज़रूरत का पता चलना चाहिए।
यह विचार कि लागू करने की बारीकियाँ कोड पढ़ने वालों को सोच की प्रक्रिया, लक्ष्य और प्राथमिकताएँ पहचानने में मदद करें, डिज़ाइन इंटेंट कहलाता है। आप अपने वेरिएबलो के नाम कैसे रखते हैं, आपका फंक्शन कौन-कौन से पैरामीटर लेता है, और चीज़ों को कैसे एब्स्ट्रैक्ट किया जाता है, ये सब ऐसी जगहें हैं जहाँ डिज़ाइन इंटेंट ज़ाहिर हो सकता है, चाहे अच्छी तरह हो या बुरी तरह।
मैं पूरे यकीन से मानता हूँ कि कोई इंजीनियरिंग डिज़ाइन लागू करते समय डिज़ाइन इंटेंट उन सबसे ज़रूरी चीज़ों में से एक है जिन पर ध्यान देना चाहिए। यह उन चीज़ों में से एक है जो सॉफ्टवेयर इंजीनियरिंग को प्रोग्रामिंग से अलग करती हैं।
सॉफ्टवेयर इंजीनियरिंग वह है जो प्रोग्रामिंग के साथ तब होता है जब आप उसमें समय और दूसरे प्रोग्रामर जोड़ते हैं।
— Russ Cox
डिज़ाइन इंटेंट हर क्षेत्र में मायने रखता है
मैं एक मैकेनिकल इंजीनियर के तौर पर काम करता हूँ और इंजेक्शन मोल्ड डिज़ाइन करता हूँ, ज़्यादातर चिकित्सा उपकरणों के लिए। मेरे सारे डिज़ाइन तैयार होते ही सीधे मशीन शॉप में पहुँच जाते हैं, जहाँ सारे टुकड़े बनने शुरू होते हैं और एक साथ जोड़े जाते हैं। चूँकि उन्हें यह नहीं पता होता कि हर डिज़ाइन बनाते समय मेरे दिमाग में क्या-क्या चल रहा था, इसलिए मुझे डिज़ाइन के ज़रिए ही अपना इंटेंट दिखाने का तरीका ढूँढना पड़ता है।
कई बार कुछ हिस्से खास तौर पर अहम होते हैं। या तो ग्राहक ने कहा होता है कि उन्हें वहाँ खास तंग टॉलरेंस चाहिए, या फिर मोल्ड के जुड़ने का तरीका किसी वजह से बेहद सटीकता माँगता है। तो मशीनिस्टों को टुकड़े ऐसे बनाने में मदद करने के लिए कि ज़रूरी हिस्सों पर सटीकता को प्राथमिकता मिले, मुझे ऐसी जगहें छोड़नी पड़ती हैं जो जान-बूझकर चौकोर हों, या जिन्हें किसी खास तरीके से वाइस में आसानी से फँसाया जा सके। इस तरह उनके लिए सबसे आसान रास्ता मेरे लिए सबसे अच्छा नतीजा देता है।
कुछ जगहें ऐसी भी होती हैं जहाँ माप इतनी अहम नहीं होती। उदाहरण के लिए, अगर मैं डिज़ाइन में सिर्फ हवा निकलने के लिए एक छेद बनाता हूँ, तो उसे 6mm जैसा कोई आम, सुविधाजनक नाप देता हूँ।
जब वे इस छेद को बनाते हैं और नापने जाते हैं कि यह कैसा बना, तो अगर उन्हें 5.99mm जैसा आंकड़ा दिखता है, तो वे सोचेंगे, "ठीक है, यह शायद 6mm ही होना चाहिए था, तो मैं काफी करीब हूँ," और उन्हें CAD या स्पेसिफिकेशन ड्रॉइंग पर माप दोबारा जाँचने की ज़रूरत ही नहीं पड़ेगी। इसके बजाय, अगर मैं उसे 5.87mm जैसा कोई असामान्य नाप देता, तो वे उसे देखते और उनकी पहली प्रतिक्रिया यह होती:
- अरे नहीं, कहीं मैंने इसे बहुत छोटा तो नहीं बना दिया? इसे 6mm ही होना चाहिए था?
- (वे CAD जाकर देखते हैं और पाते हैं कि उनका छेद ठीक है, बस नाप असामान्य है।)
- हम्म्म। ज़रूर इस छेद का नाप किसी वजह से असामान्य है। हो सकता है यह सचमुच ज़रूरी हो, या ग्राहक ने यहाँ कोई खास छेद माँगा हो। मुझे Ryan से जाकर बात करनी पड़ेगी और देखना पड़ेगा कि इस छेद में ऐसी क्या खास बात है।
- (धड़ाम! वे एल्युमिनियम का टुकड़ा बड़े नाज़ुक अंदाज़ में मेरी मेज़ पर रख देते हैं।)
- (उन्हें पता चलता है कि इस छेद में कुछ खास नहीं है, मैंने बस अजीब नाप चुन लिया था, और यह सारी अतिरिक्त मेहनत और चिंता बेकार गई।)
- अरे, वह Ryan तो बड़ा नमूना निकला। (बड़बड़ाहट, गाली, बड़बड़ाहट)
यह सब इसलिए होता है क्योंकि मेरे डिज़ाइन का हर फैसला उसे देखने और उस पर काम करने वाले दूसरे लोगों को कुछ न कुछ बताता है, चाहे मैं ऐसा चाहूँ या न चाहूँ। उन्हें उसमें मतलब ढूँढना ही पड़ता है, क्योंकि उनके पास इसके अलावा और कोई जानकारी होती ही नहीं! इसलिए यह कहीं बेहतर है कि मैं समय निकालकर अपने डिज़ाइन में अर्थपूर्ण और सोच-समझकर डाली गई जानकारी रखूँ।
अनाज: एक परिचय
अब बात करते हैं कि कोड में डिज़ाइन इंटेंट कैसे ज़ाहिर किया जा सकता है। इसके लिए हम Exercism के एक अभ्यास से उदाहरण लेते हैं। हाल ही में मैंने एक छात्र के साथ Bash ट्रैक वाले अनाज अभ्यास के उनके हल पर काम किया। अनाज एक ऐसा अभ्यास है जो गेहूँ और शतरंज की बिसात की समस्या पर आधारित है। संक्षेप में, शतरंज की बिसात के पहले खाने पर गेहूँ का एक दाना रखा जाता है। अगले खाने पर दो दाने रखे जाते हैं। उसके अगले खाने पर चार दाने। और इसी तरह, हर खाने में पिछले खाने से दोगुने दाने। छात्रों से कहा जाता है कि वे हर अलग खाने की वैल्यू निकालने का तरीका ढूँढें, साथ ही बिसात पर मौजूद दानों की कुल संख्या भी।
इस खास छात्र ने कुल जोड़ निकालने का काफी चतुर तरीका निकाला।
bc <<< 'ibase=16;FFFFFFFFFFFFFFFF'
bc एक कमांड-लाइन कैलकुलेटर है। आप इसे गणित के स्ट्रिंग दे सकते हैं, और यह उन्हें हल कर देता है, बहुत बड़े पूर्णांकों और दशमलव संख्याओं के लिए भी। Bash में bc के बिना गणना करने के और भी तरीके हैं, लेकिन सरलता के लिए हम यह देखेंगे कि bc इस्तेमाल करते समय इंटेंट कैसे ज़ाहिर किया जा सकता है, या नहीं किया जा सकता।
यह हल इसलिए काम करता है क्योंकि पूरा अभ्यास दो की घातों के इर्द-गिर्द घूमता है। और जहाँ दो की घातें हैं, वहाँ बाइनरी है, और जहाँ बाइनरी है, वहाँ हेक्साडेसिमल है2!
यह चतुर हल तो है, लेकिन यह कोड हमें क्या बता रहा है? कि यहाँ हेक्साडेसिमल ज़रूरी है? कि यह समस्या मूल रूप से 16 के इर्द-गिर्द घूमती है? समस्या का विवरण दोबारा पढ़ने के बाद साफ़ हो जाता है कि इनमें से कोई बात नहीं है। छात्र और मैंने कुछ तरीके सोचे कि इंटेंट को और साफ़ तरीके से कैसे ज़ाहिर किया जा सके। ये कुछ बातें हैं जो हम सोचकर निकाले:
पहला विकल्प: बाइनरी
चूँकि यहाँ ढेर सारी चीज़ें दोगुनी हो रही हैं (और इसलिए ढेर सारी दो की घातें भी), तो चलिए देखते हैं कि बाइनरी में क्या हो रहा है, शायद इससे मदद मिले।
पहले खाने पर 1 दाना है। बाइनरी में यह भी 0b1 होगा (यहाँ 0b का मतलब बस "यह एक बाइनरी संख्या है", और असली संख्या 1 है)।
दूसरे खाने पर 2 दाने हैं। बाइनरी में 0b10। अब तक का कुल 3 है (या 0b11)।
तीसरे खाने पर 4 (0b100) दाने हैं। अब तक का कुल: 7 (0b111)।
चौथे खाने पर 8 दाने (0b1000) हैं। अब तक का कुल: 15 (0b1111)।
क्या आपको पैटर्न दिख रहा है?
हर खाना एक और बाइनरी अंक दिखाता है, और सबको जोड़ने पर सिर्फ 1 की एक लंबी कतार बनती है।
छात्र के हल में हम F की जगह 64 बार 1 लिख सकते हैं (हर खाने के लिए एक)!
bc <<< "ibase=2;1111111111111111111111111111111111111111111111111111111111111111"
ज़्यादा सोच-समझकर लिखा हुआ, क्योंकि यह समस्या में जो दिया है उससे ज़्यादा मेल खाता है। लेकिन हम रोबोट की भाषा नहीं बोलते। 1 की एक लंबी, लगभग गिनी न जा सकने वाली कतार शायद कोई सुधार नहीं है।
दूसरा विकल्प: ब्रूट फोर्स गणना
ठीक है, तो शायद हम दशमलव के अलावा बाकी सारी संख्या प्रणालियाँ छोड़ दें। क्यों न हम कोड को वैसा बनाएँ जैसे हम हाथ से शतरंज की बिसात पर दाने गिनकर कुल निकालते हैं, हर खाने के दाने गिनते हुए?
total=0
current_grains=1
for square in {1..64}; do
total=$( bc <<< "$total + $current_grains" )
current_grains=$( bc <<< "$current_grains * 2" )
done
echo "$total"
यह पढ़ने और समझने में कहीं ज़्यादा आसान है। यह कोड साफ़ दिखाता है कि शतरंज की बिसात पर खानों की संख्या एक अहम कारक है, और हर खाने पर दोगुना होने का असर भी। मुझे लगता है यह पहले वाले हल से बेहतर है।
लेकिन।
यह धीमा है। लूप चलाना, जोड़ना, और बार-बार किसी बाहरी कमांड को कॉल करना? ये सब मिलकर चलने का समय कुछ धीमा कर देते हैं। अब, क्या यह कोई बहुत बड़ी बात है? नहीं। अगर आप इसे Bash में स्क्रिप्ट कर रहे हैं, तो आपने शायद पहले ही तय कर लिया है कि आप पर गति की कोई बाध्यता नहीं है। लेकिन क्या यह और बेहतर हो सकता है? हाँ।
तीसरा विकल्प: सीधी गणना
तो, इटरेशन किए बिना इन सबका जोड़ कैसे निकालें?
चलिए इसी समस्या का एक छोटा रूप लेते हैं: 5 खानों वाली एक शतरंज की बिसात3।
इन पाँच खानों पर दानों की संख्या यह होगी:
---------------------
| 1 | 2 | 4 | 8 |16 |
---------------------
और यहाँ कुल होगा: 1 + 2 + 4 + 8 + 16 = 31। हम्म। 31 से मुझे अभी कुछ साफ़ नहीं दिख रहा। चलिए थोड़ा बड़ा लेते हैं।
ठीक है, तो 6 खानों वाली बिसात कैसी रहेगी? इस बार मैं हर खाने के नीचे चलता कुल दिखाऊँगा, ताकि जोड़ने में मदद मिले।
-------------------------
| 1 | 2 | 4 | 8 |16 |32 |
| | 3 | 7 |15 |31 |63 |
-------------------------
और जोड़: 1 + 2 + 4 + 8 + 16 + 32 = 63। हम्म... अब मुझे पैटर्न की हल्की झलक दिखने लगी है, लेकिन पक्का करने के लिए एक और कर लेते हैं।
7 खाने:
-----------------------------
| 1 | 2 | 4 | 8 |16 |32 |64 |
| | 3 | 7 |15 |31 |63 |127|
-----------------------------
1 + 2 + 4 + 8 + 16 + 32 +64 = 127। दिख गया? 31, 63, 127 इन वैल्यू से कुछ पहचान रहे हैं?
ये दो की घातें लगभग हैं। वास्तव में, ये अगली दो की घात से एक कम हैं।
एक और उदाहरण, ताकि बात पक्की हो जाए। सोचिए, 12 खानों वाली एक शतरंज की बिसात। वह एक है, जिसे 11 बार दोगुना किया गया (गणित की दुनिया में यह 2^11 है): 2048। उसे फिर दोगुना कीजिए, तो 4096 मिलता है (2^12)। तो... अगर हमारा पैटर्न सही है, तो चलता कुल 4096 से एक कम होगा, यानी 4095। और अगर हम इसे जोड़ें, तो बिल्कुल यही मिलता है: 1 + 2 + 4 + 8 + 16 + 32 + 64 + 128 + 256 + 512 + 1024 + 2048 = 4095।
दूसरे तरीके से कहें तो, सभी
nखानों का कुल निकालने के लिए आपको दो की घात एक बढ़ानी होगी और नतीजे में से 1 घटाना होगा।
खाना 64 पर दानों की संख्या 2^63 है (याद है, गिनती शून्य से शुरू होती है?)। तो, अगर हम खाना 64 तक के सारे खानों के कुल दाने निकालना चाहते हैं, तो हमें 2^64 निकालकर 1 घटाना होगा।
धमाका!
Bash में यह ऐसा दिखेगा:
bc <<< "2^64 - 1"
जब आप बाइनरी में देखते हैं कि क्या हो रहा है, तो यह समझ में आ जाता है। बाइनरी में सारे 64 खानों का कुल क्या था?
0b1111... # 64 ones
और सैद्धांतिक रूप से 65वें खाने पर दानों की संख्या क्या होगी?
0b10000... # 1 and 64 zeros
1 और 64 शून्य से 64 एक तक कैसे पहुँचेंगे? 1 घटाकर।
और इससे हमें क्या अतिरिक्त फ़ायदा मिलता है? तो, अब हमारे पास कुल के लिए एक साफ़, पढ़ने लायक एक्सप्रेशन है। यह इटरेशन नहीं करता, इसलिए परफ़ॉर्मेंस अच्छी है। और इसमें संख्या 64 है, जो शतरंज की बिसात के खानों की संख्या है, और यह साफ़-साफ़ ज़ाहिर किए गए डिज़ाइन इंटेंट का अच्छा उदाहरण है। अगर किसी वजह से 1000 साल बाद दुनिया 7x7 शतरंज की बिसात को मानक बना ले, तो वह भविष्य का इंजीनियर (जो शायद Bash 6.1 इस्तेमाल कर रहा होगा) स्क्रिप्ट देखेगा, समझ जाएगा कि आपका मतलब क्या था, और 64 को बदलकर 49 कर देगा। बढ़िया!
सोच-समझकर काम करते रहिए, दोस्तों
कोई चीज़ लागू करते समय चीज़ों को इधर-उधर फेंकना और जो पहला हल चल जाए उसे पकड़ लेना आसान होता है। जब तक आप समस्या को खँगाल रहे हैं, यह ठीक है। लेकिन जब आप ज़रूरी हिस्सों को पूरी तरह समझ लें, और आपके पास चीज़ों को अच्छी तरह निखारने का समय हो, तो ध्यान रखिए कि हर एल्गोरिदम, हर वेरिएबल का नाम, और यहाँ तक कि आपका खाली स्थान भी समस्या की, ज़रूरी शर्तों की, और सारे टुकड़ों के आपस में जुड़ने की एक तस्वीर खींचे।
-
Thom Holwerda का कॉमिक भी देखिए। ↩
-
अगर आपको लगे कि बाइनरी और हेक्साडेसिमल की गिनती में आप थोड़ा जंग खा गए हैं, तो @kytrinyx How to Count किताब का सुझाव देते हैं। बेशर्मी से अपना प्रचार करते हुए, मैंने हाल ही में बाइनरी और हेक्साडेसिमल पर दो-तीन ब्लॉग पोस्ट भी लिखी हैं। ↩
-
मुझे नहीं पता यह कैसे होगा। शायद हम प्यादों से ही एक-दूसरे पर भाले चलवा लें। ↩