एल्गोरिदम को हाथ से लिखे गए छोरों के लिए प्राथमिकता दें?


10

आप में से कौन सा अधिक पठनीय लगता है? हाथ से लिखा हुआ लूप:

for (std::vector<Foo>::const_iterator it = vec.begin(); it != vec.end(); ++it)
{
    bar.process(*it);
}

या एल्गोरिथ्म मंगलाचरण:

#include <algorithm>
#include <functional>

std::for_each(vec.begin(), vec.end(),
              std::bind1st(std::mem_fun_ref(&Bar::process), bar));

मुझे आश्चर्य है कि अगर std::for_eachवास्तव में इसके लायक है, तो इस तरह के एक सरल उदाहरण को देखते हुए पहले से ही इतने कोड की आवश्यकता होती है।

इस मामले पर आपके क्या विचार हैं?


2
C ++ के लिए, उत्तर बहुत स्पष्ट है। लेकिन अन्य भाषाओं में, दोनों समान हैं (उदाहरण पायथन:, map(bar.process, vec)हालांकि साइड इफेक्ट्स के लिए मानचित्र हतोत्साहित किया जाता है और मानचित्र पर सूची बोध / जनरेटर अभिव्यक्तियों की सिफारिश की जाती है)।

5
और फिर वहाँ भी है BOOST_FOREACH...
बेंजामिन बैनिएर

तो, प्रभावी STL में आइटम ने आपको मना नहीं किया? stackoverflow.com/questions/135129/…
नौकरी

जवाबों:


18

एक कारण यह है कि लैम्ब्डा को पेश किया गया था, और यह इसलिए भी है क्योंकि यहां तक ​​कि मानक कॉमिटे भी मानता है कि दूसरा रूप बेकार है। पहले फॉर्म का उपयोग करें, जब तक कि आपको C ++ 0x और लैम्ब्डा समर्थन न मिले।


2
दूसरे फॉर्म को सही ठहराने के लिए उसी तर्क का इस्तेमाल किया जा सकता है: मानक समिति ने माना कि यह इतना बेहतर था कि उन्होंने इसे और बेहतर बनाने के लिए लैंबडास की शुरुआत की। :-p (+1 फिर भी)
कोनराड रुडोल्फ

मुझे नहीं लगता कि दूसरा बुरा है। लेकिन यह बेहतर हो सकता है। उदाहरण के लिए आपने जानबूझकर बार ऑब्जेक्ट को ऑपरेटर () नहीं जोड़कर उपयोग करने के लिए कठिन बना दिया। इसका उपयोग करके यह लूप में कॉल बिंदु को सरल करता है और कोड को सुव्यवस्थित बनाता है।
मार्टिन यॉर्क

नोट: दो प्रमुख शिकायतों के कारण लैम्ब्डा समर्थन जोड़ा गया था। 1) मानक बायलरप्लेट को फंक्शनलर्स को पैरामीटर पास करने की आवश्यकता होती है। 2) कॉल प्वाइंट पर कोड नहीं होना। आपका कोड इनमें से किसी को भी संतुष्ट नहीं करता है क्योंकि आप केवल एक विधि कह रहे हैं। तो 1) मापदंडों को पारित करने के लिए कोई अतिरिक्त कोड 2) कोड पहले से ही कॉल बिंदु पर नहीं है, इसलिए लैम्ब्डा कोड मेंटेनेंस में सुधार नहीं करेगा। तो हाँ लंबोदर एक महान इसके अतिरिक्त हैं। यह उनके लिए अच्छा तर्क नहीं है।
मार्टिन जॉर्ज

9

हमेशा उस वैरिएंट का उपयोग करें जो सबसे अच्छा वर्णन करता है कि आप क्या करने का इरादा रखते हैं। अर्थात्

प्रत्येक तत्व के लिए xमें vec, कर bar.process(x)

अब, उदाहरणों की जांच करते हैं:

std::for_each(vec.begin(), vec.end(),
              std::bind1st(std::mem_fun_ref(&Bar::process), bar));

हमारे पास for_eachवहाँ भी है - यिप्पीह । हमारे पास वह [begin; end)सीमा है जिस पर हम काम करना चाहते हैं।

सिद्धांत रूप में, एल्गोरिथ्म बहुत अधिक स्पष्ट था और इस प्रकार किसी भी हाथ से लिखे गए कार्यान्वयन पर बेहतर था। लेकिन फिर ... बाइंडर्स? Memfun? मूल रूप से C ++ इंटर्न कैसे एक सदस्य समारोह की पकड़ पाने के लिए? अपने काम के लिए, मुझे उनकी परवाह नहीं है! न ही मैं इस क्रिया, खौफनाक सिंटैक्स से पीड़ित होना चाहता हूं।

अब दूसरी संभावना:

for (std::vector<Foo>::const_iterator it = vec.begin(); it != vec.end(); ++it)
{
    bar.process(*it);
}

दी, यह पहचान करने के लिए एक सामान्य पैटर्न है, लेकिन ... पुनरावृत्तियां बनाना, लूपिंग, इंक्रीमेंटिंग, डेरेफेरिंग। ये सभी चीजें हैं जिन्हें मैं अपना काम पूरा करने के लिए परवाह नहीं करता हूं।

बेशक, यह waay पहले समाधान की तुलना में बेहतर लग रहा है (कम से कम, पाश शरीर लचीला और काफी स्पष्ट है), लेकिन अभी भी, यह वास्तव में नहीं है कि महान। हम इस का उपयोग करेंगे अगर हमारे पास कोई बेहतर संभावना नहीं थी, लेकिन शायद हमारे पास है ...

एक बेहतर तरीका?

अब वापस for_each। क्या सचमुच यह कहना बहुत अच्छा नहीं होगा for_each और जो ऑपरेशन किया जाना है, उसमें भी लचीला होना चाहिए? सौभाग्य से, सी ++ 0x लैम्ब्डा के बाद से, हम हैं

for_each(v.begin(), v.end(), [&](const Foo& x) { bar.process(x); })

अब जब हमने कई संबंधित स्थितियों के लिए एक सार, सामान्य समाधान ढूंढ लिया है, तो यह ध्यान देने योग्य है कि इस विशेष मामले में, एक परम # 1 पसंदीदा है :

foreach(const Foo& x, vec) bar.process(x);

यह वास्तव में उससे ज्यादा स्पष्ट नहीं हो सकता। शुक्र है, C ++ 0x एक समान सिंटैक्स बिल्ट-इन है !


2
कहा "समान वाक्यविन्यास" for (const Foo& x : vec) bar.process(x);, या const auto&यदि आप की तरह का उपयोग कर।
जॉन पूर्डी

const auto&संभव है? पता नहीं था कि - महान जानकारी!
दारियो

8

क्योंकि यह इतना अपठनीय है?

for (unsigned int i=0;i<vec.size();i++) {
{
    bar.process(vec[i]);
}

4
क्षमा करें, लेकिन किसी कारण से यह "सेंसर" कहती है जहां मेरा मानना ​​है कि आपका कोड होना चाहिए।
मतीन उल्हाक

6
आपका कोड एक संकलक चेतावनी देता है, क्योंकि एक के intसाथ तुलना करना size_tखतरनाक है। उदाहरण के लिए, हर कंटेनर के साथ अनुक्रमण काम नहीं करता है std::set
fredoverflow

2
यह कोड केवल इंडेक्सिंग ऑपरेटर वाले कंटेनरों के लिए काम करेगा, जिनमें से कुछ ही हैं। यह एल्गोरिथ्म को असमान रूप से नीचे रखता है - क्या होगा यदि एक मानचित्र <> बेहतर होगा? लूप और for_each के लिए मूल अप्रभावित रहेगा।
JBRWilkinson

2
और कितनी बार आप एक वेक्टर के लिए कोड डिजाइन करते हैं और फिर इसे एक मानचित्र के लिए स्वैप करने का निर्णय लेते हैं? जैसे कि उसके लिए डिजाइन करना भाषाओं की प्राथमिकता थी।
मार्टिन बेकेट

2
इंडेक्स द्वारा संग्रह से अधिक आयतन को हतोत्साहित किया जाता है क्योंकि कुछ संग्रह में अनुक्रमण पर खराब प्रदर्शन होता है, जबकि सभी संकलक इट्रेटर का उपयोग करते समय अच्छी तरह से व्यवहार करते हैं।
slaphappy

3

सामान्य तौर पर, पहला रूप बहुत ज्यादा किसी के द्वारा पढ़ा जा सकता है जो जानता है कि एक फॉर-लूप क्या है, उनके पास कोई फर्क नहीं पड़ता है।

सामान्य तौर पर, दूसरा वह बिल्कुल भी पठनीय नहीं है: आसान यह पता लगाना कि for_each क्या कर रहा है, लेकिन अगर आपने कभी नहीं देखा है तो std::bind1st(std::mem_fun_refमैं सोच सकता हूं कि यह समझना मुश्किल है।


2

दरअसल, सी ++ 0x के साथ भी, मुझे यकीन नहीं है कि for_eachबहुत प्यार मिलेगा।

for(Foo& foo: vec) { bar.process(foo); }

रास्ता अधिक पठनीय है।

एल्गोरिदम (सी ++ में) के बारे में एक चीज जो मुझे नापसंद है, वह है कि वे पुनरावृत्तियों पर तर्क कर रहे हैं, बहुत ही मौखिक बयानों के लिए बनाते हैं।


2

यदि आपने एक फ़नकार के रूप में बार लिखा था तो यह बहुत सरल होगा:

// custom functor
class Bar
{    public: void operator()(Foo& value) { /* STUFF */ }
};


std::for_each(vec.begin(), vec.end(), Bar());

यहां कोड काफी पठनीय है।


2
यह इस तथ्य से अलग है कि आपने अभी एक फ़नकार लिखा है।
विंस्टन Ewert

यहां देखें कि मुझे क्यों लगता है कि यह कोड बदतर है।
sbi

1

मैं उत्तरार्द्ध को पसंद करता हूं क्योंकि यह साफ और स्वच्छ है। यह वास्तव में कई अन्य भाषा का हिस्सा है लेकिन C ++ में, यह लाइब्रेरी का हिस्सा है। यह वास्तव में मायने नहीं रखता।


0

अब यह मुद्दा वास्तव में C ++ के लिए विशिष्ट नहीं है, मैं दावा करूंगा।

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

एक आम उपयोग मैं अन्य भाषाओं में बहुत कुछ देखता हूं:

someCollection.forEach(someUnaryFunctor);

इसका लाभ यह है, कि आपको यह नहीं पता है कि someCollectionवास्तव में कैसे लागू किया जाता है या क्या someUnaryFunctorकरता है। आपको बस इतना जानना चाहिए कि इसकी forEachविधि संग्रह के सभी तत्वों को पुन: व्यवस्थित कर देगी, उन्हें दिए गए फ़ंक्शन में पास करेगी।

व्यक्तिगत रूप से, यदि आप स्थिति में हैं, तो उस डेटा संरचना के बारे में पूरी जानकारी रखना चाहते हैं जिसे आप हर पुनरावृत्ति चरण में क्या करना चाहते हैं, और इसके बारे में सभी जानकारी प्राप्त करना चाहते हैं, तो एक कार्यात्मक दृष्टिकोण चीजों को विशेष रूप से एक भाषा में पूरा कर रहा है, जहाँ यह काफी थकाऊ दिखाई देता है।

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


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

@Andres F: तो यह क्लीनर दिखता है? std::for_each(vec.begin(), vec.end(), std::bind1st(std::mem_fun_ref(&Bar::process), bar));और यह या तो रनटाइम पर धीमा है या समय संकलन (यदि आप भाग्यशाली हैं)। मैं यह नहीं देखता कि फ़ंक्शन कॉल के साथ प्रवाह नियंत्रण संरचनाओं को प्रतिस्थापित करना कोड को बेहतर क्यों बनाता है। यहां प्रस्तुत किसी भी विकल्प के बारे में अधिक पठनीय है।
back2dos

0

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

जहां एक एल्गोरिथ्म है जो अधिक कसकर करता है जो आप चाहते हैं तो हां आपको हाथ से लिखे गए छोरों पर एल्गोरिदम को प्राथमिकता देना चाहिए। यहाँ स्पष्ट उदाहरणों के साथ sortया stable_sortखोज कर रहे हैं या lower_bound, upper_boundया equal_range, लेकिन उनमें से अधिकांश में कुछ स्थिति है जिसमें वे एक हाथ से कोडित लूप का उपयोग करने के लिए बेहतर हैं।


0

इस छोटे से कोड स्निपेट के लिए, दोनों मेरे लिए समान रूप से पठनीय हैं। सामान्य तौर पर, मैं मैनुअल लूप त्रुटि-ग्रस्त पाता हूं और एल्गोरिदम का उपयोग करना पसंद करता हूं, लेकिन यह वास्तव में एक ठोस स्थिति पर निर्भर करता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.