एक साथ दो या दो से अधिक कंटेनरों को पुनरावृत्त करने का सबसे अच्छा तरीका क्या है


114

C ++ 11 कंटेनरों पर पुनरावृति करने के लिए कई तरीके प्रदान करता है। उदाहरण के लिए:

रेंज आधारित लूप

for(auto c : container) fun(c)

std :: for_each

for_each(container.begin(),container.end(),fun)

हालाँकि, कुछ को पूरा करने के लिए एक ही आकार के दो (या अधिक) कंटेनरों को पुनरावृत्त करने के लिए अनुशंसित तरीका क्या है:

for(unsigned i = 0; i < containerA.size(); ++i) {
  containerA[i] = containerB[i];
}

1
transformवर्तमान में क्या है #include <algorithm>?
अंकित आचार्य

असाइनमेंट लूप के बारे में: यदि दोनों वैक्टर या समान हैं, containerA = containerB;तो लूप के बजाय उपयोग करें ।
इमली

इसी तरह का सवाल: stackoverflow.com/questions/8511035/…
knedlsepp

जवाबों:


53

बल्कि पार्टी के लिए देर हो गई। लेकिन: मैं सूचकांकों पर पुनरावृति करूंगा। लेकिन शास्त्रीय forलूप के साथ नहीं, बल्कि forसूचकांकों पर रेंज-आधारित लूप के साथ:

for(unsigned i : indices(containerA)) {
    containerA[i] = containerB[i];
}

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

यह कोड एक मैनुअल, शास्त्रीय लूप का उपयोग करने के रूप में कुशल हैfor

यदि यह पैटर्न आपके डेटा में अक्सर होता है, तो एक और पैटर्न का उपयोग करने पर विचार करें जो zipदो अनुक्रमों का है और युग्म तत्वों के अनुरूप ट्यूपल्स की एक श्रृंखला उत्पन्न करता है:

for (auto& [a, b] : zip(containerA, containerB)) {
    a = b;
}

के कार्यान्वयन को zipपाठक के लिए एक अभ्यास के रूप में छोड़ दिया जाता है, लेकिन इसके कार्यान्वयन से आसानी से पालन होता है indices

(C ++ 17 से पहले आपको इसके बजाय निम्नलिखित लिखना होगा :)

for (auto items&& : zip(containerA, containerB))
    get<0>(items) = get<1>(items);

2
क्या काउंटिंग_रेन्ज को बढ़ावा देने की तुलना में आपके सूचकांक के कार्यान्वयन का कोई फायदा है? एक बस का उपयोग कर सकते हैंboost::counting_range(size_t(0), containerA.size())
सेबस्टियनके

3
@SebastianK इस मामले में सबसे बड़ा अंतर वाक्यविन्यास है: मेरा है (मेरा दावा है) इस मामले में उपयोग करने के लिए उद्देश्यपूर्ण रूप से बेहतर है। इसके अलावा, आप एक कदम आकार निर्दिष्ट कर सकते हैं। उदाहरण के लिए, लिंक किए गए Github पृष्ठ और विशेष रूप से README फ़ाइल देखें।
कोनराड रूडोल्फ

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

@SebastianK मैं स्वीकार करता हूं कि जब मैंने कोड लिखा था तो मैंने इसे लाइब्रेरी के उपयोग के बिना अलगाव में रहना काफी सरल समझा था (और यह है!)। अब मैं इसे Boost.Range के आसपास एक आवरण के रूप में लिखूंगा। उस ने कहा, मेरी लाइब्रेरी का प्रदर्शन पहले से ही इष्टतम है। मेरे कहने का मतलब यह है कि मेरे indicesकार्यान्वयन से कंपाइलर आउटपुट का उपयोग होता है जो मैनुअल लूप का उपयोग करने के समान है for। कोई उपरि नहीं है।
कोनराड रुडोल्फ

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

38

अपने विशिष्ट उदाहरण के लिए, बस उपयोग करें

std::copy_n(contB.begin(), contA.size(), contA.begin())

अधिक सामान्य मामले के लिए, आप Boost.Iterator का उपयोग कर सकते हैं zip_iterator, एक छोटे से कार्य के साथ इसे छोरों के लिए रेंज-आधारित में प्रयोग करने योग्य बनाने के लिए। ज्यादातर मामलों के लिए, यह काम करेगा:

template<class... Conts>
auto zip_range(Conts&... conts)
  -> decltype(boost::make_iterator_range(
  boost::make_zip_iterator(boost::make_tuple(conts.begin()...)),
  boost::make_zip_iterator(boost::make_tuple(conts.end()...))))
{
  return {boost::make_zip_iterator(boost::make_tuple(conts.begin()...)),
          boost::make_zip_iterator(boost::make_tuple(conts.end()...))};
}

// ...
for(auto&& t : zip_range(contA, contB))
  std::cout << t.get<0>() << " : " << t.get<1>() << "\n";

जीवंत उदाहरण।

हालांकि, पूर्ण विकसित genericity के लिए, तो आप शायद अधिक की तरह कुछ चाहते हैं इस , जो सरणियों और उपयोगकर्ता-निर्धारित प्रकार है कि सदस्य की जरूरत नहीं है के लिए सही ढंग से काम करेंगे begin()/ end()लेकिन करते है begin/ endउनके नाम स्थान में कार्य करता है। इसके अलावा, यह उपयोगकर्ता को विशेष रूप constसे zip_c...फ़ंक्शन के माध्यम से पहुंच प्राप्त करने की अनुमति देगा ।

और अगर आप मेरे जैसे अच्छे त्रुटि संदेशों के हिमायती हैं, तो आप शायद यही चाहते हैं , जो यह जाँचता है कि क्या कोई अस्थायी कंटेनर किसी भी zip_...कार्य के लिए पारित किया गया था , और यदि ऐसा है तो एक अच्छा त्रुटि संदेश प्रिंट करता है।


1
धन्यवाद! एक सवाल हालांकि, आप ऑटो && का उपयोग क्यों करते हैं, इसका क्या अर्थ है &&?
याद करते हैं

@ मीमेकस: मैं इस सवाल के माध्यम से पढ़ने की सलाह देता हूं , साथ ही मेरा यह जवाब जो थोड़े समझाता है कि कैसे कटौती और संदर्भ का पतन होता है। ध्यान दें कि autoवास्तव में टेम्पलेट पैरामीटर के रूप में ही काम करता है, और T&&एक टेम्पलेट में एक सार्वभौमिक संदर्भ पहली कड़ी में बताई गई विधि, इसलिए है auto&& v = 42के रूप में निष्कर्ष निकाला की जाएगी int&&और auto&& w = v;उसके बाद के रूप में निष्कर्ष निकाला की जाएगी int&। यह आपको प्रतियों के रूप में अस्वस्थता से मेल खाने की अनुमति देता है और दोनों को एक प्रतिलिपि बनाने के बिना, परिवर्तनशील होने देता है।
जिओ सेप

@ Xeo: लेकिन ऑटो और फोर ओवर ऑटो में & ऑटो का फायदा क्या है?
विक्टर सेहर

@ViktorSehr: यह आपको अस्थायी तत्वों से बांधने की अनुमति देता है, जैसे उत्पादन करने वालों द्वारा zip_range
Xeo

23
@ Xeo उदाहरणों के सभी लिंक टूट गए हैं।
kynan 12

34

मुझे आश्चर्य है कि किसी ने इसका उल्लेख क्यों नहीं किया:

auto ItA = VectorA.begin();
auto ItB = VectorB.begin();

while(ItA != VectorA.end() || ItB != VectorB.end())
{
    if(ItA != VectorA.end())
    {
        ++ItA;
    }
    if(ItB != VectorB.end())
    {
        ++ItB;
    }
}

पुनश्च: यदि कंटेनर का आकार मेल नहीं खाता है, तो आपको कोड को यदि विवरणों के अंदर रखना होगा।


9

हेडर में उपलब्ध कराए गए कई कंटेनरों के साथ विशिष्ट कार्य करने के कई तरीके हैं algorithm। उदाहरण के लिए, आपके द्वारा दिए गए उदाहरण में, आप std::copyलूप के लिए स्पष्ट के बजाय उपयोग कर सकते हैं ।

दूसरी ओर, लूप के लिए सामान्य से अलग कई कंटेनरों को उदारतापूर्वक बनाने के लिए कोई अंतर्निहित तरीका नहीं है। यह आश्चर्यजनक नहीं है क्योंकि इसमें पुनरावृति करने के बहुत सारे तरीके हैं। इसके बारे में सोचो: आप एक कदम से एक कंटेनर के माध्यम से पुनरावृति कर सकते हैं, दूसरे चरण के साथ एक कंटेनर; या एक कंटेनर के माध्यम से जब तक यह अंत तक नहीं जाता है तब सम्मिलित करना शुरू करें जब आप दूसरे कंटेनर के अंत तक जाते हैं; या पहले कंटेनर का एक कदम हर बार जब आप पूरी तरह से दूसरे कंटेनर से गुजरते हैं तो शुरू हो जाते हैं; या कुछ अन्य पैटर्न; एक बार में दो से अधिक कंटेनर; आदि ...

हालांकि, अगर आप अपना "for_each" स्टाइल फंक्शन बनाना चाहते थे, जो दो कंटेनरों के माध्यम से केवल सबसे कम लंबाई तक होता है, तो आप ऐसा कुछ कर सकते हैं:

template <typename Container1, typename Container2>
void custom_for_each(
  Container1 &c1,
  Container2 &c2,
  std::function<void(Container1::iterator &it1, Container2::iterator &it2)> f)
{
  Container1::iterator begin1 = c1.begin();
  Container2::iterator begin2 = c2.begin();
  Container1::iterator end1 = c1.end();
  Container2::iterator end2 = c2.end();
  Container1::iterator i1;
  Container1::iterator i2;
  for (i1 = begin1, i2 = begin2; (i1 != end1) && (i2 != end2); ++it1, ++i2) {
    f(i1, i2);
  }
}

जाहिर है आप किसी भी तरह की पुनरावृत्तियों की रणनीति बना सकते हैं जो आप एक समान तरीके से चाहते हैं।

बेशक, आप यह तर्क दे सकते हैं कि लूप के लिए सीधे आंतरिक कार्य करना इस तरह से कस्टम फ़ंक्शन लिखने से आसान है ... और आप सही होंगे, यदि आप इसे केवल एक या दो बार करने जा रहे हैं। लेकिन अच्छी बात यह है कि यह बहुत ही पुन: प्रयोज्य है। =)


ऐसा लगता है जैसे आपको लूप से पहले चलने वालों की घोषणा करनी है? मैंने यह कोशिश की: for (Container1::iterator i1 = c1.begin(), Container2::iterator i2 = c2.begin(); (i1 != end1) && (i2 != end2); ++it1, ++i2)लेकिन संकलक चिल्लाता है। क्या कोई समझा सकता है कि यह अमान्य क्यों है?
डेविड डोरिया

@DavidDoria लूप के लिए पहला भाग एक एकल स्टेटमेंट है। आप एक ही कथन में विभिन्न प्रकार के दो चर घोषित नहीं कर सकते। for (int x = 0, y = 0; ...काम क्यों करता है, इसके बारे में सोचें , लेकिन for (int x = 0, double y = 0; ...)नहीं।
wjl

1
.. आप कर सकते हैं, हालांकि, std :: pair <कंटेनर 1 :: iterator, कंटेनर 2 :: iterator> its = {c1.begin (), c2.begin ()};
लोरो 19

1
ध्यान देने वाली एक और बात यह है कि यह आसानी से C ++ 14 के साथ वैरेडिक बनाया जा सकता हैtypename...
wjl

8

मामले में जब आपको केवल 2 कंटेनरों पर एक साथ पुनरावृति करने की आवश्यकता होती है, तो सीमा श्रेणी पुस्तकालय में मानक for_each एल्गोरिथ्म का एक विस्तारित संस्करण है, जैसे:

#include <vector>
#include <boost/assign/list_of.hpp>
#include <boost/bind.hpp>
#include <boost/range/algorithm_ext/for_each.hpp>

void foo(int a, int& b)
{
    b = a + 1;
}

int main()
{
    std::vector<int> contA = boost::assign::list_of(4)(3)(5)(2);
    std::vector<int> contB(contA.size(), 0);

    boost::for_each(contA, contB, boost::bind(&foo, _1, _2));
    // contB will be now 5,4,6,3
    //...
    return 0;
}

जब आपको एक एल्गोरिथ्म में 2 से अधिक कंटेनरों को संभालने की आवश्यकता होती है, तो आपको ज़िप के साथ खेलने की आवश्यकता होती है।


आश्चर्यजनक! तुमने कैसे ढूंढा? ऐसा लगता है कि यह कहीं भी प्रलेखित नहीं है।
मिखाइल

4

एक अन्य समाधान एक लैंबडा में दूसरे कंटेनर के पुनरावृत्त के संदर्भ को कैप्चर किया जा सकता है और उस पर पोस्ट इंक्रीमेंट ऑपरेटर का उपयोग किया जा सकता है। उदाहरण के लिए सरल प्रति होगी:

vector<double> a{1, 2, 3};
vector<double> b(3);

auto ita = a.begin();
for_each(b.begin(), b.end(), [&ita](auto &itb) { itb = *ita++; })

लैम्ब्डा के अंदर आप जो कुछ भी कर सकते हैं itaऔर उसके बाद इसे बढ़ा सकते हैं। यह आसानी से कई कंटेनरों के मामले तक फैल जाता है।


3

एक श्रेणी-पुस्तकालय यह और अन्य बहुत सहायक कार्यक्षमता प्रदान करता है। निम्न उदाहरण Boost.Range का उपयोग करता है । एरिक नीब्लर की रेंजवो 3 एक अच्छा विकल्प होना चाहिए।

#include <boost/range/combine.hpp>
#include <iostream>
#include <vector>
#include <list>

int main(int, const char*[])
{
    std::vector<int> const v{0,1,2,3,4};
    std::list<char> const  l{'a', 'b', 'c', 'd', 'e'};

    for(auto const& i: boost::combine(v, l))
    {
        int ti;
        char tc;
        boost::tie(ti,tc) = i;
        std::cout << '(' << ti << ',' << tc << ')' << '\n';
    }

    return 0;
}

C ++ 17 संरचित बाइंडिंग के साथ इसे और बेहतर बनाएगा:

int main(int, const char*[])
{
    std::vector<int> const v{0,1,2,3,4};
    std::list<char> const  l{'a', 'b', 'c', 'd', 'e'};

    for(auto const& [ti, tc]: boost::combine(v, l))
    {
        std::cout << '(' << ti << ',' << tc << ')' << '\n';
    }

    return 0;
}

यह कार्यक्रम जी ++ 4.8.0 के साथ संकलन नहीं है। delme.cxx:15:25: error: no match for 'operator=' (operand types are 'std::tuple<int&, char&>' and 'const boost::tuples::cons<const int&, boost::tuples::cons<const char&, boost::tuples::null_type> >') std::tie(ti,tc) = i; ^
syam

स्टड बदलने के बाद :: टाई टू बूस्ट: टाई, यह संकलित।
सीम

मुझे संरचित बाइंडिंग (MSVC 19.13.26132.0और Windows SDK संस्करण का उपयोग करके 10.0.16299.0) संस्करण के लिए निम्नलिखित संकलित त्रुटि error C2679: binary '<<': no operator found which takes a right-hand operand of type 'const boost::tuples::cons<const char &,boost::fusion::detail::build_tuple_cons<boost::fusion::single_view_iterator<Sequence,boost::mpl::int_<1>>,Last,true>::type>' (or there is no acceptable conversion)
मिलती है

संरचित बाइंडिंग के साथ काम नहीं करते हैं boost::combine: stackoverflow.com/q/55585723/8414561
Dev Null

2

मुझे थोड़ी देर हो गई है; लेकिन आप इसका उपयोग कर सकते हैं (सी-शैली वैरेडिक फ़ंक्शन):

template<typename T>
void foreach(std::function<void(T)> callback, int count, ...) {
    va_list args;
    va_start(args, count);

    for (int i = 0; i < count; i++) {
        std::vector<T> v = va_arg(args, std::vector<T>);
        std::for_each(v.begin(), v.end(), callback);
    }

    va_end(args);
}

foreach<int>([](const int &i) {
    // do something here
}, 6, vecA, vecB, vecC, vecD, vecE, vecF);

या यह (एक फंक्शन पैरामीटर पैक का उपयोग करके):

template<typename Func, typename T>
void foreach(Func callback, std::vector<T> &v) {
    std::for_each(v.begin(), v.end(), callback);
}

template<typename Func, typename T, typename... Args>
void foreach(Func callback, std::vector<T> &v, Args... args) {
    std::for_each(v.begin(), v.end(), callback);
    return foreach(callback, args...);
}

foreach([](const int &i){
    // do something here
}, vecA, vecB, vecC, vecD, vecE, vecF);

या यह (ब्रेस-एनक्लोजर इनिशियलाइज़र सूची का उपयोग करके):

template<typename Func, typename T>
void foreach(Func callback, std::initializer_list<std::vector<T>> list) {
    for (auto &vec : list) {
        std::for_each(vec.begin(), vec.end(), callback);
    }
}

foreach([](const int &i){
    // do something here
}, {vecA, vecB, vecC, vecD, vecE, vecF});

या आप यहाँ वैक्टर में शामिल हो सकते हैं: दो वैक्टर को मिलाने का सबसे अच्छा तरीका क्या है? और फिर बड़े वेक्टर पर पुनरावृति।


0

यहाँ एक प्रकार है

template<class ... Iterator>
void increment_dummy(Iterator ... i)
    {}

template<class Function,class ... Iterator>
void for_each_combined(size_t N,Function&& fun,Iterator... iter)
    {
    while(N!=0)
        {
        fun(*iter...);
        increment_dummy(++iter...);
        --N;
        }
    }

उदाहरण उपयोग

void arrays_mix(size_t N,const float* x,const float* y,float* z)
    {
    for_each_combined(N,[](float x,float y,float& z){z=x+y;},x,y,z);    
    }
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.