लूप के लिए रेंज-आधारित 'कई सरल एल्गोरिदम को दर्शाती है?


81

एल्गोरिथ्म समाधान:

std::generate(numbers.begin(), numbers.end(), rand);

रेंज आधारित लूप समाधान के लिए:

for (int& x : numbers) x = rand();

मैं std::generateC ++ 11 में श्रेणी-आधारित फ़ोर -लूप से अधिक क्रिया का उपयोग क्यों करना चाहूंगा ?


14
Composability? ओह कोई बात नहीं, पुनरावृत्तियों के साथ एल्गोरिदम अक्सर वैसे भी कंपोजिट नहीं होते हैं: ...
आर। मार्टिनो फर्नांडीस

2
... जो नहीं हैं begin()और end()?
the_mandrill

6
@jrok मुझे उम्मीद है कि बहुत से लोग rangeअब तक अपने टूलबॉक्स में एक समारोह है। (यानी for(auto& x : range(first, last)))
आर। मार्टिनो फर्नांडीस

14
boost::generate(numbers, rand); // ♪
Xeo

5
@JamesBrock हमने सी ++ चैट रूम में अक्सर इस पर चर्चा की है (यह कहीं न कहीं ट्रांसक्रिप्शन में होना चाहिए: पी)। मुख्य मुद्दा यह है कि एल्गोरिदम अक्सर एक पुनरावृत्त लौटाते हैं, और दो पुनरावृत्तियों को लेते हैं।
आर। मार्टिनो फर्नांडीस

जवाबों:


79

पहला संस्करण

std::generate(numbers.begin(), numbers.end(), rand);

हमें बताता है कि आप मूल्यों का एक क्रम उत्पन्न करना चाहते हैं।

दूसरे संस्करण में पाठक को यह पता लगाना होगा कि वह खुद बाहर है।

टाइपिंग पर बचत आमतौर पर सबॉप्टीमल है, क्योंकि यह अक्सर पढ़ने के समय में खो जाता है। अधिकांश कोड को टाइप किए जाने की तुलना में बहुत अधिक पढ़ा जाता है।


13
टाइपिंग पर बचत? अच्छा मैं समझा। क्यों ओह हमारे पास "संकलन-समय की स्वच्छता जांच" और "कीबोर्ड पर कुंजी मारने" के लिए एक ही शब्द क्यों है? :)
fredoverflow

25
" टाइपिंग पर बचत आमतौर पर सबप्टीमल है " बकवास; यह सब आपके द्वारा उपयोग की जा रही लाइब्रेरी के बारे में है। std :: जनरेट लंबा है क्योंकि आपको numbersबिना किसी कारण के दो बार निर्दिष्ट करना होगा । इसलिए: boost::range::generate(numbers, rand);। कोई कारण नहीं है कि आप अच्छी तरह से निर्मित पुस्तकालय में कम और अधिक सुपाठ्य दोनों कोड नहीं रख सकते।
निकोल बोलस

9
यह सब पाठक की नज़र में है। लूप संस्करण के लिए सबसे प्रोग्रामिंग पृष्ठभूमि के साथ समझा जा सकता है: संग्रह के प्रत्येक तत्व के लिए रैंड मूल्य डालें। Std :: Generate के लिए हाल ही में C ++ जानने की आवश्यकता होती है, या यह अनुमान लगाने पर कि वास्तव में "आइटम संशोधित करें" का अर्थ है, "उत्पन्न मान वापस नहीं"।
हाइड

2
यदि आप केवल एक कंटेनर का हिस्सा संशोधित करना चाहते हैं तो आप std::generate(number.begin(), numbers.begin()+3, rand)यह नहीं कर सकते हैं ? इसलिए मुझे लगता है कि numberदो बार निर्दिष्ट करना कभी-कभी उपयोगी हो सकता है।
मार्सन माओ

7
@MarsonMao: यदि आपके पास केवल दो-तर्क हैं std::generate(), तो आप इसके बजाय std::generate(slice(number.begin(), 3), rand)एक काल्पनिक सीमा स्लाइसिंग सिंटैक्स के साथ कर सकते हैं या बेहतर भी कर सकते हैं जैसे कि सीमा के हिस्से के लचीले विनिर्देश को अनुमति देते समय std::generate(number[0:3], rand)दोहराव को हटा numberदेता है। तीन तर्क से शुरू होने वाला उल्टा करना std::generate()अधिक कठिन है।
रेयान

42

लूप के लिए सीमा आधारित है या नहीं, इससे कोई फर्क नहीं पड़ता है, यह केवल कोष्ठक के अंदर कोड को सरल करता है। एल्गोरिदम स्पष्ट हैं कि वे इरादे दिखाते हैं ।


30

व्यक्तिगत रूप से, मेरा प्रारंभिक पढ़ना:

std::generate(numbers.begin(), numbers.end(), rand);

"हम एक सीमा में सब कुछ करने के लिए असाइन कर रहे हैं। सीमा है numbers। असाइन किए गए मान यादृच्छिक हैं"।

मेरी प्रारंभिक रीडिंग:

for (int& x : numbers) x = rand();

"हम एक श्रेणी में सब कुछ के लिए कुछ कर रहे हैं। सीमा है numbers। हम जो करते हैं वह एक यादृच्छिक मूल्य प्रदान करता है।"

वे बहुत समान हैं, लेकिन समान नहीं हैं। एक प्रशंसनीय कारण जो मैं पहली बार पढ़ने के लिए उकसाना चाहता हूं, वह यह है कि मुझे लगता है कि इस कोड के बारे में सबसे महत्वपूर्ण तथ्य यह है कि यह सीमा तक असाइन होता है। तो आपका "मैं क्यों चाहूंगा ..." है। मैं उपयोग करता हूं generateक्योंकि C ++ का std::generateअर्थ है "रेंज असाइनमेंट"। जैसा कि btw करता है std::copy, दोनों के बीच का अंतर वह है जो आप से असाइन कर रहे हैं।

हालांकि, इस बारे में भ्रमित करने वाले कारक हैं। लूप्स के लिए रेंज-आधारित में व्यक्त करने का एक अधिक प्रत्यक्ष तरीका है कि रेंज है numbers, इट्रेटर-आधारित एल्गोरिदम की तुलना में। यही कारण है कि लोग रेंज-आधारित एल्गोरिथ्म लाइब्रेरी पर काम करते हैं: संस्करण की boost::range::generate(numbers, rand);तुलना में बेहतर दिखता है std::generate

जैसा कि उस के खिलाफ, int&लूप के लिए आपके रेंज-आधारित में एक शिकन है। क्या होगा यदि रेंज का मान प्रकार नहीं है int, तो हम यहां कुछ कष्टप्रद सूक्ष्म रूप से कर रहे हैं जो इस पर निर्भर करता है कि यह परिवर्तनीय है int&, जबकि generateकोड केवल randतत्व के लिए असाइन किए जाने से वापसी पर निर्भर करता है । भले ही मूल्य प्रकार है int, मैं अभी भी यह सोचने के लिए रुक सकता हूं कि यह है या नहीं। इसलिए auto, जो प्रकारों के बारे में सोचने से चूक जाता है जब तक कि मुझे नहीं दिखता कि क्या सौंपा गया है - auto &xमैं कहता हूं "रेंज तत्व का संदर्भ लें, जो भी प्रकार हो सकता है"। C ++ 03 में वापस, एल्गोरिदम (क्योंकि वे फ़ंक्शन टेम्पलेट हैं) सटीक प्रकार छिपाने का तरीका था, अब वे एक तरीका है।

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


क्या आपने कभी उपयोगकर्ता-परिभाषित प्रकार देखा है operator int&()? :)
fredoverflow

@FredOverflow के int&साथ बदल जाता है SomeClass&और अब आपको रूपांतरण ऑपरेटरों और एकल-पैरामीटर निर्माणकर्ताओं के बारे में चिंता करनी होगी जो चिह्नित नहीं हैं explicit
टेम्प्लेक्स

@FredOverflow: ऐसा मत सोचो। यही कारण है कि अगर ऐसा कभी होता है, तो मैं इसकी उम्मीद नहीं करूंगा और इससे कोई फर्क नहीं पड़ता कि मैं अब इसके बारे में कितना पागल हूं, यह मुझे काट लेगा अगर मैं ऐसा नहीं सोचता हूं तो ;-) एक प्रॉक्सी वस्तु हो सकती है। ओवरलोडिंग द्वारा काम करते हैं operator int&()और operator int const &() const, लेकिन फिर यह ओवरलोडिंग operator int() constऔर काम कर सकता है operator=(int)
स्टीव जेसोप

1
@rhalbersma: मुझे नहीं लगता कि आपको कंस्ट्रक्टर्स के बारे में चिंता करनी होगी, क्योंकि एक नॉन-कॉस्ट रेफ एक अस्थायी के लिए बाध्य नहीं करता है। यह केवल संदर्भ प्रकारों के लिए रूपांतरण ऑपरेटर है।
स्टीव जेसोप

23

मेरी राय में, प्रभावी एसटीएल आइटम 43: "एल्गोरिथ्म को हाथ से लिखे गए छोरों को कॉल करना पसंद है।" अभी भी एक अच्छी सलाह है।

मैं आमतौर पर begin()/ end()नरक से छुटकारा पाने के लिए आवरण कार्य लिखता हूं । यदि आप ऐसा करते हैं, तो आपका उदाहरण इस तरह दिखाई देगा:

my_util::generate(numbers, rand);

मेरा मानना ​​है कि यह मंशा को संप्रेषित करने और पठनीयता में लूप के आधार पर सीमा को पार करता है।


यह कहने के बाद, मुझे यह स्वीकार करना होगा कि C ++ 98 में कुछ STL एल्गोरिथ्म में असंबद्ध कोड की उपज होती है और "Prefer एल्गोरिथ्म कॉल टू हैंड-राइट्ड लूप्स" का पालन करना एक अच्छा विचार नहीं था। सौभाग्य से, लैम्ब्डा ने इसे बदल दिया है।

हर्ब सटर से निम्नलिखित उदाहरण पर विचार करें : लैंबडास, लैंबडस एवरीवेयर

कार्य: v में पहला तत्व खोजें जो है > xऔर < y

लंबोदर के बिना:

auto i = find_if( v.begin(), v.end(),
bind( logical_and<bool>(),
bind(greater<int>(), _1, x),
bind(less<int>(), _1, y) ) );

लंबोदर के साथ

auto i=find_if( v.begin(), v.end(), [=](int i) { return i > x && i < y; } );

1
प्रश्न के लिए थोड़ा सा रूढ़िवादी। केवल पहला वाक्य प्रश्न को संबोधित करता है।
डेविड रॉड्रिग्ज - dribeas

@ DavidRodríguez-dribeas हां। दूसरी छमाही बता रही है कि मुझे क्यों लगता है कि आइटम 43 अभी भी एक अच्छी सलाह है।
अली

Boost.Lambda के साथ, यह C ++ लंबो कार्यों के साथ भी बेहतर है: ऑटो i = find_if (v.begin (), v.end (), _1> x && _1 <y);
sdkljhdf hda

1
रैपरों के लिए +1। वैसा ही कर रहे हैं। 1 दिन (या शायद 2 ...) से मानक में होना चाहिए था
मैके

22

में मेरी राय, मैनुअल पाश, हालांकि शब्दाडंबर को कम कर सकते हैं, readabitly का अभाव है:

for (int& x : numbers) x = rand();

मैं इस लूप का उपयोग संख्याओं द्वारा परिभाषित 1 रेंज को इनिशियलाइज़ करने के लिए नहीं करूंगा , क्योंकि जब मैं इसे देखता हूं, तो मुझे लगता है कि यह संख्याओं की संख्या से अधिक है, लेकिन वास्तविकता में यह (संक्षेप में) नहीं है, अर्थात रेंज से पढ़ना , यह रेंज के लिए लिख रहा है।

जब आप उपयोग करते हैं तो आशय बहुत स्पष्ट होता है std::generate

1. इस संदर्भ में प्रारंभिक का मतलब कंटेनर के तत्वों को सार्थक मूल्य देना है ।


5
क्या सिर्फ इसलिए नहीं कि आप लूप्स के लिए रेंज-आधारित नहीं हैं, हालांकि? यह मुझे स्पष्ट रूप से स्पष्ट लगता है कि यह कथन प्रत्येक तत्व को श्रेणी में निर्दिष्ट करता है। यह स्पष्ट है कि उत्पन्न वही iff है जिससे आप परिचित हैं std::generate, जिसे C ++ प्रोग्रामर माना जा सकता है (यदि वे परिचित नहीं हैं, तो वे इसे देखेंगे, वही परिणाम)।
स्टीव जेसप

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

1
@ DavidRodríguez-dribeas: यह उत्तर अन्य दो, IMO से काफी भिन्न है। यह इस कारण से ड्रिल करने की कोशिश करता है कि लेखक कोड के एक टुकड़े को दूसरे की तुलना में अधिक स्पष्ट / समझ में आता है। अन्य लोग इसे विश्लेषण के बिना बताते हैं। यही कारण है कि मुझे यह एक बहुत दिलचस्प लगता है इसका जवाब देने के लिए :-)
स्टीव जेसप

1
@SteveJessop: आप इस निष्कर्ष पर आने के लिए लूप के शरीर में देखना चाहते हैं कि आप वास्तव में संख्याएँ उत्पन्न कर रहे हैं , लेकिन std::generateकेवल देखने के मामले में , कोई कह सकता है कि इस फ़ंक्शन द्वारा कुछ उत्पन्न किया जा रहा है; क्या कुछ है कि समारोह में तीसरे तर्क से जवाब दिया है। मुझे लगता है कि यह बहुत बेहतर है।
नवाज

1
@SteveJessop: तो इसका मतलब है कि आप अल्पसंख्यक हैं। मैं कोड लिखूंगा जो बहुमत के लिए स्पष्ट है: पी। बस एक आखिरी: मैंने कहीं भी यह नहीं कहा कि अन्य लोग पाश को उसी तरह से पढ़ेंगे जैसे मैंने किया था। मैंने कहा (बल्कि इसका मतलब है ) यह लूप पढ़ने का एक तरीका है जो मुझे गुमराह कर रहा है, और क्योंकि लूप बॉडी है, इसलिए अलग-अलग प्रोग्रामर इसे पढ़ने के लिए अलग-अलग तरीके से पढ़ेंगे कि वहाँ क्या हो रहा है; वे अलग-अलग कारणों से ऐसे लूप के उपयोग पर आपत्ति जता सकते हैं, जो सभी उनकी धारणा के अनुसार सही हो सकते हैं।
नवाज

9

कुछ चीजें हैं जो आप (बस) रेंज-आधारित छोरों के साथ नहीं कर सकते हैं जो एल्गोरिदम इनपुट के रूप में पुनरावृत्तियों को लेते हैं। उदाहरण के लिए std::generate:

एक वितरण से चर के साथ limit(अपवर्जित, limitएक मान्य पुनरावृत्ति है numbers) को दूसरे वितरण से चर के साथ और शेष भरें ।

std::generate(numbers.begin(), limit, rand1);
std::generate(limit, numbers.end(), rand2);

Iterator- आधारित एल्गोरिदम आपको उस सीमा पर बेहतर नियंत्रण देता है जिस पर आप काम कर रहे हैं।


8
जबकि पठनीयता एल्गोरिदम को पसंद करने के लिए एक बड़ा कारण है , यह एकमात्र उत्तर है जो दिखाता है रेंज-आधारित फॉर-लूप है कि एल्गोरिदम क्या है , और केवल इसलिए इसका उपसमुच्चय है और इसलिए कुछ भी नहीं चित्रित किया जा सकता ...
K-ballo

6

के विशेष मामले के लिए std::generate, मैं पठनीयता / इरादे के मुद्दे पर पिछले उत्तरों से सहमत हूं। std :: उत्पन्न मुझे एक अधिक स्पष्ट संस्करण लगता है। लेकिन मैं मानता हूं कि यह एक तरह से स्वाद का मामला है।

उस ने कहा, मेरे पास एक और कारण है कि एसटीडी को न फेंकना :: एल्गोरिथ्म - कुछ एल्गोरिदम हैं जो कुछ डेटा प्रकारों के लिए विशिष्ट हैं।

सबसे सरल उदाहरण होगा std::fill। सामान्य संस्करण को प्रदान की गई सीमा से अधिक लूप के रूप में लागू किया गया है, और इस संस्करण का उपयोग टेम्पलेट को तत्काल करते समय किया जाएगा। लेकिन हमेशा नहीं। उदाहरण के लिए, यदि आप इसे एक सीमा प्रदान करते हैं, जो एक है std::vector<int>- अक्सर यह वास्तव memsetमें हुड के तहत कॉल करेगा , एक बहुत तेज और बेहतर कोड उपज।

इसलिए मैं यहां एक दक्षता कार्ड खेलने की कोशिश कर रहा हूं।

आपका हाथ से लिखा लूप एक std :: एल्गोरिथ्म संस्करण जितना तेज़ हो सकता है, लेकिन यह शायद ही तेज़ हो। और इससे भी अधिक, std :: एल्गोरिथ्म विशेष कंटेनरों और प्रकारों के लिए विशेष हो सकता है और यह साफ एसटीएल इंटरफेस के तहत किया जाता है।


3

मेरा जवाब शायद और नहीं होगा। यदि हम C ++ 11 के बारे में बात कर रहे हैं, तो हो सकता है (अधिक पसंद नहीं)। उदाहरण के लिएstd::for_each लैम्ब्डा के साथ भी उपयोग करने के लिए वास्तव में कष्टप्रद है:

std::for_each(c.begin(), c.end(), [&](ExactTypeOfContainedValue& x)
{
    // do stuff with x
});

लेकिन इसके लिए रेंज-आधारित का उपयोग करना बेहतर है:

for (auto& x : c)
{
    // do stuff with x
}

दूसरी ओर, यदि हम C ++ 1y के बारे में बात कर रहे हैं, तो मैं तर्क दूंगा कि नहीं, एल्गोरिदम को सीमा के आधार पर नहीं माना जाएगा। C ++ मानक समिति में एक अध्ययन समूह है जो C ++ में सीमाएँ जोड़ने के प्रस्ताव पर काम कर रहा है, और वहाँ भी पॉलीमोर्फिक लैम्ब्डा पर काम किया जा रहा है। रेंजर्स जोड़ीदार और पॉलिमॉर्फिक लैम्ब्डा की जोड़ी का उपयोग करने की आवश्यकता को हटा देंगे जिससे आप लैम्बडा के सटीक तर्क प्रकार को निर्दिष्ट नहीं कर पाएंगे। इसका मतलब यह है कि std::for_eachइस तरह इस्तेमाल किया जा सकता है (इसे एक कठिन तथ्य के रूप में न लें, यह सिर्फ आज के सपने दिखते हैं):

std::for_each(c.range(), [](x)
{
    // do stuff with x
});

तो बाद के मामले में, एल्गोरिथ्म का लाभ यह होगा कि []लैम्बडा के साथ लिखकर आप शून्य-कैप्चर निर्दिष्ट करते हैं? यह है कि सिर्फ एक लूप बॉडी लिखने के साथ तुलना करके, आपने चर लुकअप संदर्भ से कोड का एक हिस्सा अलग कर दिया है जो कि यह शाब्दिक रूप से प्रकट होता है। अलगाव आमतौर पर पाठक के लिए सहायक होता है, पढ़ने के बारे में सोचने के लिए कम।
स्टीव जेसप

1
कब्जा करने की बात नहीं है। मुद्दा यह है कि बहुरूपी लैम्ब्डा के साथ आपको स्पष्ट रूप से यह बताने की आवश्यकता नहीं होगी कि एक्स का प्रकार क्या है।
sdkljhdf hda

1
उस मामले में यह मुझे लगता है कि इस काल्पनिक C ++ 1y में, for_eachएक लंबोदर के साथ उपयोग किए जाने पर भी अभी भी व्यर्थ है। foreach + कैप्चरिंग लैम्ब्डा वर्तमान में लूप के लिए रेंज-आधारित लिखने का एक क्रियात्मक तरीका है, और यह थोड़ा कम वर्बोज़ तरीका बन जाता है, लेकिन लूप की तुलना में अभी भी अधिक है। ऐसा नहीं है कि मुझे लगता है कि आपको बचाव करना चाहिए for_each, निश्चित रूप से, लेकिन आपके उत्तर को देखने से पहले मैं सोच रहा था कि यदि प्रश्नकर्ता एल्गोरिदम को मारना चाहता था, तो वह for_eachसभी संभावित लक्ष्यों में से सबसे नरम के रूप में चुना जा सकता था ;-)
स्टीव जेसप

बचाव के लिए नहीं जा रहा है for_each, लेकिन इसके लिए रेंज-आधारित पर एक छोटा सा फायदा है - आप इसे समानांतर रूप से आसानी से समानांतर बना सकते हैं इसे समानांतर में बदल सकते हैं parallel_for_each(यदि आप पीपीएल का उपयोग करते हैं, और ऐसा करने के लिए थ्रेड-सुरक्षित मानते हैं) । :-D
sdkljhdf hda

@ लेगो आपका "छोटा" लाभ वास्तव में एक "बड़ा" लाभ है यदि इसे इस तथ्य के लिए सामान्यीकृत किया जाए कि std::algorithmएस 'कार्यान्वयन उनके इंटरफ़ेस के पीछे छिपा हुआ है और मनमाने ढंग से जटिल हो सकता है (या मनमाने ढंग से अनुकूलित)।
क्रिश्चियन राऊ

1

एक बात पर ध्यान दिया जाना चाहिए कि एक एल्गोरिथ्म व्यक्त करता है कि क्या किया जाता है, कैसे नहीं।

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

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


1

रेंज-लूप के लिए बस इतना ही है। जब तक पाठ्यक्रम में बदलाव नहीं किया जाता है।

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

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