सभी <एल्गोरिथम> फ़ंक्शन केवल श्रेणियां क्यों लेते हैं, कंटेनर नहीं?


49

इसमें कई उपयोगी कार्य हैं <algorithm>, लेकिन सभी "सीक्वेंस" पर चलते हैं - पुनरावृत्तियों के जोड़े। जैसे, अगर मेरे पास एक कंटेनर है और std::accumulateउस पर चलना पसंद है , तो मुझे लिखना होगा:

std::vector<int> myContainer = ...;
int sum = std::accumulate(myContainer.begin(), myContainer.end(), 0);

जब सब करने का इरादा है:

int sum = std::accumulate(myContainer, 0);

जो मेरी नजर में थोड़ा अधिक पठनीय और स्पष्ट है।

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

एक आवरण फ़ंक्शन लिखना आसान है जो एक कंटेनर लेता है begin()और उस end()पर कॉल करता है, लेकिन मानक पुस्तकालय में ऐसे सुविधा कार्य शामिल नहीं हैं।

मैं इस एसटीएल डिजाइन विकल्प के पीछे तर्क जानना चाहता हूं।


7
क्या एसटीएल आम तौर पर सुविधा आवरण प्रदान करता है, या क्या यह पुराने C ++ का यहाँ-के-उपकरण-अब-गो-शूट-खुद-ए-फ़ुट पॉलिसी का पालन करता है?
बजे किलन फथ

2
रिकॉर्ड के लिए: अपने स्वयं के आवरण को लिखने के बजाय आपको Boost.Range में एल्गोरिथ्म आवरण का उपयोग करना चाहिए; इस मामले में,boost::accumulate
ecatmur

जवाबों:


40

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

आपके अनुभव में यह एक दुर्लभ विशेष मामला हो सकता है , लेकिन वास्तव में पूरे कंटेनर विशेष मामला है, और मनमाना रेंज सामान्य मामला है।

आपने पहले ही ध्यान दिया है कि आप वर्तमान इंटरफ़ेस का उपयोग करके पूरे कंटेनर केस को कार्यान्वित कर सकते हैं , लेकिन आप कन्वेक्शन नहीं कर सकते।

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


एक रैपर फ़ंक्शन लिखना आसान है जो कंटेनर लेता है और उस पर कॉल () और एंड () शुरू होता है, लेकिन मानक पुस्तकालय में ऐसे सुविधा कार्य शामिल नहीं हैं

यह सच है, विशेष रूप से मुक्त कार्यों के बाद से std::beginऔर std::endअब शामिल हैं।

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

template <typename Container>
void sort(Container &c) {
  sort(begin(c), end(c));
}

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

लेकिन हम कम से कम हर मामले को कवर करते हैं जहां हम एक पूर्ण कंटेनर पर काम करना चाहते हैं, है ना? खैर, काफी नहीं। विचार करें

std::for_each(c.rbegin(), c.rend(), foo);

यदि हम कंटेनरों पर परिचालन को पीछे करना चाहते हैं , तो हमें मौजूदा एल्गोरिथ्म के अनुसार एक और विधि (या तरीकों की जोड़ी) की आवश्यकता है।


तो, रेंज-आधारित दृष्टिकोण सरल अर्थों में अधिक सामान्य है:

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

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

जैसा कि टिप्पणियों में बताया गया है, Boost.Range मानक में बदलाव की आवश्यकता के बिना एक नया दृष्टिकोण प्रदान करता है।


9
मुझे नहीं लगता कि कोई भी, ओपी शामिल है, हर एक विशेष मामले के लिए अधिभार जोड़ने का सुझाव दे रहा है। यहां तक ​​कि अगर "पूरे कंटेनर" "एक मनमाना रेंज" की तुलना में कम सामान्य था, तो यह निश्चित रूप से "पूरे कंटेनर, उलट" की तुलना में कहीं अधिक सामान्य है। इसे f(c.begin(), c.end(), ...)ओवरलोड की संख्या को दोगुना करने से रोकने के लिए इसे प्रतिबंधित करें , और शायद केवल सबसे अधिक इस्तेमाल किया जाने वाला अधिभार (हालांकि आप इसे निर्धारित करते हैं)। इसके अलावा, इट्रेटर एडेप्टर पूरी तरह से ऑर्थोगोनल हैं (जैसा कि आप ध्यान दें, वे पायथन में ठीक काम करते हैं, जिनके पुनरावृत्तियों बहुत अलग तरीके से काम करते हैं और आपके पास जितनी शक्ति है, उसके बारे में बात नहीं करते हैं)।

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

23
नोट करें कि एसटीएल ने रेंज ऑब्जेक्ट की पेशकश की है, तो कंटेनर संस्करण सभी मामलों को कवर करेगा ; उदा std::sort(std::range(start, stop))

3
इसके विपरीत: रचना योग्य कार्यात्मक एल्गोरिदम (जैसे नक्शा और फ़िल्टर) एक एकल ऑब्जेक्ट लेते हैं जो एक संग्रह का प्रतिनिधित्व करता है और एक एकल ऑब्जेक्ट लौटाता है, वे निश्चित रूप से पुनरावृत्तियों की एक जोड़ी के समान कुछ भी उपयोग नहीं करते हैं।
svick

3
एक मैक्रो ऐसा कर सकता है: #define MAKE_RANGE(container) (container).begin(), (container).end()</ jk>
शाफ़्ट फ्रीक

21

इस विषय पर हर्ब सटर द्वारा एक लेख दिया गया है । मूल रूप से, समस्या अधिभार अस्पष्टता है। निम्नलिखित को देखते हुए:

template<typename Iter>
void sort( Iter, Iter ); // 1

template<typename Iter, typename Pred>
void sort( Iter, Iter, Pred ); // 2

और निम्नलिखित जोड़ना:

template<typename Container>
void sort( Container& ); // 3

template<typename Container, typename Pred>
void sort( Container&, Pred ); // 4

भेद करना मुश्किल होगा 4और 1ठीक से।

प्रस्ताव, जैसा कि प्रस्तावित है, लेकिन अंततः C ++ 0x में शामिल नहीं है, ने इसे हल कर दिया है, और इसका उपयोग करना भी संभव है enable_if। कुछ एल्गोरिदम के लिए, यह कोई समस्या नहीं है। लेकिन उन्होंने इसके खिलाफ फैसला किया।

अब यहां सभी टिप्पणियों और उत्तरों को पढ़ने के बाद, मुझे लगता है कि rangeऑब्जेक्ट सबसे अच्छा समाधान होगा। मुझे लगता है कि मैं देख लूंगा Boost.Range


1
ठीक है, typename Iterएक सख्त भाषा के लिए बस प्रतीत होता है कि वह बहुत ज्यादा सुस्त है। मैं जैसे template<typename Container> void sort(typename Container::iterator, typename Container::iterator); // 1और template<template<class> Container, typename T> void sort( Container<T>&, std::function<bool(const T&)> ); // 4आदि (जो शायद अस्पष्टता की समस्या को हल करेगा) को पसंद करेंगे
व्लाद

@ व्लाद: दुर्भाग्य से, यह सादे पुराने सरणियों के लिए काम नहीं करेगा, क्योंकि T[]::iteratorउपलब्ध नहीं है। इसके अलावा, उचित पुनरावृत्त किसी भी संग्रह का नेस्टेड प्रकार होने के लिए बाध्य नहीं है, यह परिभाषित करने के लिए पर्याप्त है std::iterator_traits
अग्निगुरिकु

@firegurafiku: खैर, कुछ बेसिक TMP ट्रिक्स के साथ विशेष केस के लिए सरणियाँ आसान हैं।
व्लाद

11

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

लेकिन हाँ, निश्चय ही गलत है। हम begin/endया तो या से निर्मित एक सीमा वस्तु के साथ बेहतर होता begin/length; अब हमारे पास _nइसके बजाय कई प्रत्यय एल्गोरिदम हैं।


5

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

संक्षेप में, यह संभवत: इसलिए नहीं है क्योंकि पूरे कंटेनर उपयोगकर्ताओं को एक अतिरिक्त फ़ंक्शन कॉल पैरामीटर टाइप करने से बचाने के लिए अतिरिक्त टेम्पलेट्स का एक सेट बनाए रखने के लिए यह परेशानी के लायक नहीं है।


9
यह मुझे सत्ता हासिल नहीं करेगा, यह सच है - लेकिन अंत में, न तो करता है std::getline, और फिर भी, यह पुस्तकालय में है। यह कहने के लिए एक बहुत आगे जा सकता है कि विस्तारित नियंत्रण संरचनाएं मुझे शक्ति प्राप्त नहीं करती हैं, क्योंकि मैं केवल ifऔर का उपयोग करके सब कुछ कर सकता था goto। हाँ, अनुचित तुलना, मुझे पता है;) मुझे लगता है कि मैं विनिर्देश / कार्यान्वयन / रखरखाव बोझ किसी भी तरह समझ सकते हैं, लेकिन यह केवल एक छोटे से आवरण हम यहाँ के बारे में बात कर रहे हैं, इसलिए ..
घातक-गिटार

एक छोटे रैपर को कोड करने के लिए कुछ भी खर्च नहीं होता है और शायद यह लाइब्रेरी में होने का कोई मतलब नहीं है।
Ebasconp

-1

अब तक, http://en.wikipedia.org/wiki/C+11#Range-based_for_loop एक अच्छा विकल्प है std::for_each। निरीक्षण करें, कोई स्पष्ट पुनरावृत्तियों:

int a[5] = {1, 2, 3, 4, 5};
for (auto &i: a) { i *= 2; }

( Https://stackoverflow.com/a/694534/2097284 से प्रेरित होकर )


1
यह केवल <algorithm>उस वास्तविक हिस्से के लिए हल करता है, जो सभी वास्तविक एलगोस की जरूरत नहीं है beginऔर endपुनरावृत्तियों - लेकिन लाभ को ओवरस्टैट नहीं किया जा सकता है! जब मैंने पहली बार 2009 में C ++ 03 की कोशिश की, तो मैंने लूपिंग के बॉयलरप्लेट के कारण चलने वाले से दूर रखा, और सौभाग्य से या नहीं, इस समय मेरी परियोजनाओं ने इसकी अनुमति दी। 2014 में C ++ 11 पर पुनः आरंभ करना, यह एक अविश्वसनीय उन्नयन था, भाषा C ++ हमेशा होनी चाहिए, और अब मैं इसके बिना नहीं रह सकता auto &it: them:)
अंडरस्कोर_
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.