'छोरों' के लिए कई 'लिखने के साफ तरीके


98

कई आयामों वाले एक सरणी के लिए, हमें आमतौर पर forइसके प्रत्येक आयाम के लिए एक लूप लिखने की आवश्यकता होती है । उदाहरण के लिए:

vector< vector< vector<int> > > A;

for (int k=0; k<A.size(); k++)
{
    for (int i=0; i<A[k].size(); i++)
    {
        for (int j=0; j<A[k][i].size(); j++)
        {
            do_something_on_A(A[k][i][j]);
        }
    }
}

double B[10][8][5];
for (int k=0; k<10; k++)
{
    for (int i=0; i<8; i++)
    {
        for (int j=0; j<5; j++)
        {
            do_something_on_B(B[k][i][j]);
        }
    }
}

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


62
स्पष्ट उत्तर यह है कि आप नहीं। आप मैक्रोज़ (या किसी अन्य तकनीक) का उपयोग करके एक नई भाषा नहीं बनाते हैं; आपके बाद आने वाला व्यक्ति कोड को पढ़ने में असमर्थ होगा।
जेम्स कान्जे

17
जब आपके पास वेक्टर का एक वेक्टर होता है, तो यह खराब डिज़ाइन का संकेत है।
मारून

5
@ निम: आप इसे 1 फ्लैट ऐरे से कर सकते हैं (यकीन नहीं होता कि यह बेहतर है)।
Jarod42

16
मुझे लगता है कि आप संभावित O(n) = n^3कोड छिपाना नहीं चाहते ...
poy

36
@ TC1: और फिर मुझे पढ़ने में मुश्किल होगी। यह सभी व्यक्तिगत प्राथमिकताओं का सवाल है और यह वास्तव में यहाँ हाथ में सवाल के साथ मदद नहीं करता है।
ेरेऑन

जवाबों:


281

पहली बात यह है कि आप ऐसी डेटा संरचना का उपयोग नहीं करते हैं। यदि आपको तीन आयामी मैट्रिक्स की आवश्यकता है, तो आप एक को परिभाषित करते हैं:

class Matrix3D
{
    int x;
    int y;
    int z;
    std::vector<int> myData;
public:
    //  ...
    int& operator()( int i, int j, int k )
    {
        return myData[ ((i * y) + j) * z + k ];
    }
};

या यदि आप इंडेक्स का उपयोग करना चाहते हैं [][][], तो आपको operator[] एक प्रॉक्सी की आवश्यकता होती है।

एक बार जब आप ऐसा कर लेते हैं, यदि आप पाते हैं कि आपने लगातार प्रस्तुत करना है जैसा कि आपने प्रस्तुत किया है, तो आप एक पुनरावृत्त को उजागर करते हैं जो इसका समर्थन करेगा:

class Matrix3D
{
    //  as above...
    typedef std::vector<int>::iterator iterator;
    iterator begin() { return myData.begin(); }
    iterator end()   { return myData.end();   }
};

तो आप बस लिखें:

for ( Matrix3D::iterator iter = m.begin(); iter != m.end(); ++ iter ) {
    //  ...
}

(या केवल:

for ( auto& elem: m ) {
}

अगर आपके पास C ++ 11 है।)

और अगर आपको इस तरह के पुनरावृत्तियों के दौरान तीन इंडेक्सों की आवश्यकता है, तो एक इटेटर बनाना संभव है जो उन्हें उजागर करता है:

class Matrix3D
{
    //  ...
    class iterator : private std::vector<int>::iterator
    {
        Matrix3D const* owner;
    public:
        iterator( Matrix3D const* owner,
                  std::vector<int>::iterator iter )
            : std::vector<int>::iterator( iter )
            , owner( owner )
        {
        }
        using std::vector<int>::iterator::operator++;
        //  and so on for all of the iterator operations...
        int i() const
        {
            ((*this) -  owner->myData.begin()) / (owner->y * owner->z);
        }
        //  ...
    };
};

21
इस उत्तर को और अधिक उत्कीर्ण किया जाना चाहिए क्योंकि यह एकमात्र ऐसा है जो समस्या के वास्तविक स्रोत से संबंधित है।
ेरेऑन

5
यह उत्तर हो सकता है, लेकिन मैं सहमत नहीं हूं कि यह एक अच्छा है। शायद x10 गुना धीमा संकलन समय और शायद x10 धीमा डिबग (अधिक हो सकता है) कोड के साथ बहुत सारे क्रिप्टिक टेम्पलेट कोड। मेरे लिए निश्चित रूप से मूल कोड मेरे लिए अधिक स्पष्ट है ...
गोर्केम

10
@ बेइरफ़ ... और भी बहुत, बहुत धीमा। क्योंकि C और C ++ में बहुआयामी सरणियों को वास्तव में इस अर्थ में नेस्टेड एरेस कहा जाता है कि बाहरी आयाम एनर्जी एरे की ओर संकेत करते हैं। इन नीडिंत सरणियों को तब स्मृति में चारों ओर बिखरे हुए, प्रभावी ढंग से किसी भी प्रीफेटिंग और कैशिंग को हराया जाता है। मुझे उन उदाहरणों का पता है जहां किसी ने vector<vector<vector<double> > >3-आयामी क्षेत्र का प्रतिनिधित्व करने के लिए एक कोड लिखा था । कोड ऊपर समाधान के बराबर 10 की एक speedup के परिणामस्वरूप पुनर्लेखन
माइकल जंगली

5
@beehorf आपको कोई टेम्प्लेट कोड कहां दिखाई देता है? (व्यवहार में, Matrix3Dशायद एक टेम्प्लेट होना चाहिए, लेकिन यह बहुत ही सीधा टेम्प्लेट है।) और आपको केवल डीबग करना होगा Matrix3D, हर बार जब आपको 3 डी मैट्रिक्स की आवश्यकता होती है, तो आप डिबगिंग में बहुत अधिक समय बचाते हैं। स्पष्टता के रूप में: की std::vector<std::vector<std::vector<int>>>तुलना में स्पष्ट कैसे है Matrix3D? यह उल्लेख नहीं करने के लिए कि Matrix3Dइस तथ्य को लागू करता है कि आपके पास एक मैट्रिक्स है, जबकि नेस्टेड वैक्टर को रैग किया जा सकता है, और यह कि उपरोक्त संभवतः काफी तेज है।
बजे जेम्स कान्जे

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

44

forलूप्स को छिपाने के लिए मैक्रो का उपयोग करना बहुत भ्रमित कर सकता है, बस कुछ पात्रों को बचाने के लिए। मैं इसके बजाय रेंज-फॉर लूप का उपयोग करूंगा :

for (auto& k : A)
    for (auto& i : k)
        for (auto& j : i)
            do_something_on_A(j);

बेशक आप की जगह ले सकता auto&के साथ const auto&करता है, तो आप कर रहे हैं, वास्तव में, डेटा को संशोधित नहीं।


3
मान लें कि OP C ++ 11 का उपयोग कर सकता है।
जारोद42

1
@herohuyongtao पुनरावृत्तियों के मामले में। यहां अधिक मुहावरेदार हो सकता है, लेकिन ऐसे मामले हैं जहां आप तीन intचर चाहते हैं ।
जेम्स कान्जे

1
और क्या ऐसा नहीं होना चाहिए do_something_on_A(*j)?
जेम्स कान्जे

1
@ जेफ्रे आह, हाँ। प्रकार बाहर वर्तनी के लिए एक और कारण। (मैं का उपयोग करते हैं लगता है autoके लिए kऔर i, असली समस्या यह है कि वह नेस्टेड वैक्टर का उपयोग कर रहा है उचित हो सकता है, सिवाय इसके कि यह अभी भी गलत स्तर पर समस्या का हल।।)
जेम्स Kanze

2
@धारा kवैक्टर का एक संपूर्ण वेक्टर है (अच्छी तरह से यह एक संदर्भ), एक सूचकांक नहीं है।
यक्क - एडम नेवेरुमोंट

21

ऐसा कुछ मदद कर सकता है:

 template <typename Container, typename Function>
 void for_each3d(const Container &container, Function function)
 {
     for (const auto &i: container)
         for (const auto &j: i)
             for (const auto &k: j)
                 function(k);
 }

 int main()
 {
     vector< vector< vector<int> > > A;     
     for_each3d(A, [](int i){ std::cout << i << std::endl; });

     double B[10][8][5] = { /* ... */ };
     for_each3d(B, [](double i){ std::cout << i << std::endl; });
 }

इसे एन-एरी बनाने के लिए हमें कुछ टेम्प्लेट जादू की आवश्यकता है। सबसे पहले हमें यह निर्धारित करने के लिए SFINAE संरचना तैयार करनी चाहिए कि क्या यह मान या कंटेनर है। मूल्यों के लिए डिफ़ॉल्ट कार्यान्वयन, और एरे और कंटेनर के प्रत्येक प्रकार के लिए विशेषज्ञता। @Zeta कैसे नोट करता है, हम मानक कंटेनरों को नेस्टेड iteratorप्रकार द्वारा निर्धारित कर सकते हैं (आदर्श रूप से हमें जांचना चाहिए कि प्रकार का उपयोग रेंज-बेस के साथ किया जा सकता है forया नहीं)।

 template <typename T>
 struct has_iterator
 {
     template <typename C>
     constexpr static std::true_type test(typename C::iterator *);

     template <typename>
     constexpr static std::false_type test(...);

     constexpr static bool value = std::is_same<
         std::true_type, decltype(test<typename std::remove_reference<T>::type>(0))
     >::value;
 };

 template <typename T>
 struct is_container : has_iterator<T> {};

 template <typename T>
 struct is_container<T[]> : std::true_type {};

 template <typename T, std::size_t N>
 struct is_container<T[N]> : std::true_type {}; 

 template <class... Args>
 struct is_container<std::vector<Args...>> : std::true_type {};

का कार्यान्वयन for_eachसीधा है। डिफ़ॉल्ट फ़ंक्शन कॉल करेगा function:

 template <typename Value, typename Function>
 typename std::enable_if<!is_container<Value>::value, void>::type
 rfor_each(const Value &value, Function function)
 {
     function(value);
 }

और विशेषज्ञता खुद को पुनरावर्ती कहेगी:

 template <typename Container, typename Function>
 typename std::enable_if<is_container<Container>::value, void>::type
 rfor_each(const Container &container, Function function)
 {
     for (const auto &i: container)
         rfor_each(i, function);
 }

और वोइला:

 int main()
 {
     using namespace std;
     vector< vector< vector<int> > > A;
     A.resize(3, vector<vector<int> >(3, vector<int>(3, 5)));
     rfor_each(A, [](int i){ std::cout << i << ", "; });
     // 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,

     std::cout << std::endl;
     double B[3][3] = { { 1. } };
     rfor_each(B, [](double i){ std::cout << i << ", "; });
     // 1, 0, 0, 0, 0, 0, 0, 0, 0,
 }

इसके अलावा यह पॉइंटर्स (ढेर में आवंटित सरणियों) के लिए काम नहीं करेगा।


@herohuyongtao बाधाओं के साथ हम Containerदूसरों के लिए और उनके लिए दो विशेषज्ञता लागू कर सकते हैं।
11

1
@herohuyongtao मैंने K-ary foreach का उदाहरण दिया।
जाली

1
@fasked: is_container : has_iterator<T>::valueमेरे उत्तर से उपयोग करें और आपको हर प्रकार के लिए एक विशेषज्ञता लिखने की आवश्यकता नहीं है, क्योंकि हर कंटेनर में एक iteratorटाइफाइड होना चाहिए । बेझिझक मेरे जवाब से किसी भी चीज़ का पूरी तरह से उपयोग कर सकते हैं, आपका पहले से बेहतर है।
जीटा

इसके लिए @Zeta +1। जैसा कि मैंने बताया कि Containerअवधारणा से मदद मिलेगी।
जाली

::iteratorएक चलने योग्य सीमा नहीं बनाते हैं। int x[2][3][4]यह पूरी तरह से चलने योग्य है, जैसा कि struct foo { int x[3]; int* begin() { return x; } int* end() { return x+3; } }; मुझे यकीन नहीं है कि T[]विशेषज्ञता क्या करने वाली है?
यक्क - एडम नेवरामोंट

17

अधिकांश उत्तर बस यह प्रदर्शित करते हैं कि कैसे C ++ को असंगत सिंटैक्टिक एक्सटेंशन, IMHO में बदल दिया जा सकता है।

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

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

for (auto& k : A)
for (auto& i : k)
for (auto& current_A : i)
    do_something_on_A(current_A);

बिना किसी स्पष्ट शब्दार्थ के सदिश के वेक्टर के सदिश की गूढ़ परिभाषा के अनुरूप है।


10
#include "stdio.h"

#define FOR(i, from, to)    for(int i = from; i < to; ++i)
#define TRIPLE_FOR(i, j, k, i_from, i_to, j_from, j_to, k_from, k_to)   FOR(i, i_from, i_to) FOR(j, j_from, j_to) FOR(k, k_from, k_to)

int main()
{
    TRIPLE_FOR(i, j, k, 0, 3, 0, 4, 0, 2)
    {
        printf("i: %d, j: %d, k: %d\n", i, j, k);
    }
    return 0;
}

अद्यतन: मुझे पता है, कि आपने इसके लिए कहा था, लेकिन आप बेहतर उपयोग नहीं करेंगे कि :)


5
मुझे पता है कि ओपी ने क्या मांगा, लेकिन गंभीरता से ... यह ओफिसकेशन के अद्भुत उदाहरण की तरह दिखता है। मान लिया TRIPLE_FORगया कि कुछ हेडर में परिभाषित किया गया है, जब मैं यहां 'TRIPLE_FOR देखूं तो मुझे क्या सोचना है।
जेम्स कान्जे

2
हां, मुझे लगता है, आप सही हैं :) मुझे लगता है, मैं इसे एक उदाहरण के रूप में यहां छोड़ दूँगा कि यह मैक्रोज़ का उपयोग करके किया जा सकता है, लेकिन एक नोट जोड़ें कि ऐसा नहीं करना बेहतर है :) मैं सिर्फ जागता हूं ऊपर, इस सवाल को दिमाग के लिए एक छोटे वार्म-अप के रूप में उपयोग करने का फैसला किया।
FreeNickname

5

एक विचार एक चलने योग्य छद्म-कंटेनर वर्ग को लिखना है जो आपके द्वारा अनुक्रमित किए जाने वाले सभी मल्टी-इंडेक्स टुपल्स के सेट को "समाहित" करता है। यहां कोई कार्यान्वयन नहीं है क्योंकि इसमें बहुत लंबा समय लगेगा लेकिन विचार यह है कि आपको लिखना चाहिए ...

multi_index mi (10, 8, 5);
  //  The pseudo-container whose iterators give {0,0,0}, {0,0,1}, ...

for (auto i : mi)
{
  //  In here, use i[0], i[1] and i[2] to access the three index values.
}

यहाँ सबसे अच्छा जवाब imo।
दाविदघ

4

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

//This is roughly what we want for values
template<class input_type, class func_type> 
void rfor_each(input_type&& input, func_type&& func) 
{ func(input);}

//This is roughly what we want for containers
template<class input_type, class func_type>
void rfor_each(input_type&& input, func_type&& func) 
{ for(auto&& i : input) rfor_each(i, func);}

हालाँकि, यह (स्पष्ट रूप से) हमें अस्पष्टता देता है। इसलिए हम यह पता लगाने के लिए SFINAE का उपयोग करते हैं कि फ़ंक्शन में वर्तमान इनपुट फिट बैठता है या नहीं

//Compiler knows to only use this if it can pass input to func
template<class input_type, class func_type>
auto rfor_each(input_type&& input, func_type&& func) ->decltype(func(input)) 
{ return func(input);}

//Otherwise, it always uses this one
template<class input_type, class func_type>
void rfor_each(input_type&& input, func_type&& func) 
{ for(auto&& i : input) rfor_each(i, func);}

यह अब कंटेनरों को सही ढंग से संभालता है, लेकिन कंपाइलर अभी भी इनपुट_टाइप्स के लिए इस अस्पष्टता पर विचार करता है जिसे फ़ंक्शन में पारित किया जा सकता है। तो हम एक मानक C ++ 03 ट्रिक का उपयोग करते हैं ताकि यह दूसरे पर पहला फ़ंक्शन पसंद कर सके, एक शून्य से भी गुजर रहा है, और एक जिसे हम स्वीकार करते हैं और int पसंद करते हैं, और दूसरा लेता है ...

template<class input_type, class func_type>
auto rfor_each(input_type&& input, func_type&& func, int) ->decltype(func(input)) 
{ return func(input);}

//passing the zero causes it to look for a function that takes an int
//and only uses ... if it absolutely has to 
template<class input_type, class func_type>
void rfor_each(input_type&& input, func_type&& func, ...) 
{ for(auto&& i : input) rfor_each(i, func, 0);}

बस। छह, कोड की अपेक्षाकृत सरल रेखाएं, और आप अन्य उत्तरों के विपरीत मूल्यों, पंक्तियों, या किसी अन्य उप-इकाई पर पुनरावृति कर सकते हैं।

#include <iostream>
int main()
 {

     std::cout << std::endl;
     double B[3][3] = { { 1.2 } };
     rfor_each(B[1], [](double&v){v = 5;}); //iterate over doubles
     auto write = [](double (&i)[3]) //iterate over rows
         {
             std::cout << "{";
             for(double d : i) 
                 std::cout << d << ", ";
             std::cout << "}\n";
         };
     rfor_each(B, write );
 };

संकलन और निष्पादन का प्रमाण यहाँ और यहाँ

यदि आप C ++ 11 में अधिक सुविधाजनक सिंटैक्स चाहते थे, तो आप एक मैक्रो जोड़ सकते हैं। (बाद में अप्राप्त है)

template<class container>
struct container_unroller {
    container& c;
    container_unroller(container& c_) :c(c_) {}
    template<class lambda>
    void operator <=(lambda&& l) {rfor_each(c, l);}
};
#define FOR_NESTED(type, index, container) container_unroller(container) <= [](type& index) 
//note that this can't handle functions, function pointers, raw arrays, or other complex bits

int main() {
     double B[3][3] = { { 1.2 } };
     FOR_NESTED(double, v, B) {
         std::cout << v << ", ";
     }
}

3

मैं इस उत्तर को निम्नलिखित कथन के साथ देता हूं: यह केवल तभी काम करेगा जब आप एक वास्तविक सरणी पर काम कर रहे हों - यह आपके उदाहरण का उपयोग करने के लिए काम नहीं करेगा std::vector

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

double B[3][3][3];
// ... set the values somehow
double* begin = &B[0][0][0];     // get a pointer to the first element
double* const end = &B[3][0][0]; // get a (const) pointer past the last element
for (; end > begin; ++begin) {
    (*begin) *= 2.0;
}

ध्यान दें कि उपरोक्त दृष्टिकोण का उपयोग करने से कुछ "उचित" सी ++ तकनीकों के उपयोग की भी अनुमति मिलती है:

double do_something(double d) {
    return d * 2.0;
}

...

double B[3][3][3];
// ... set the values somehow
double* begin = &B[0][0][0];  // get a pointer to the first element
double* end = &B[3][0][0];    // get a pointer past the last element

std::transform(begin, end, begin, do_something);

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


यह अवैध है: stackoverflow.com/questions/6015080/…
ecatmur

@ecatmur: दिलचस्प है - मैं केवल काम करने के लिए मिला हूं, इसलिए मैं इस की जांच करूंगा और तदनुसार उत्तर को अपडेट / हटा दूंगा। धन्यवाद।
आइकबोड

@ecatmur: मैंने C ++ 11 मानक (खंड 8.3.4) को देखा है, और जो मैंने लिखा है वह काम करना चाहिए, और अवैध नहीं दिखता (मुझे)। आपके द्वारा प्रदान किया गया लिंक परिभाषित सरणी आकार के बाहर के सदस्यों तक पहुँचने से संबंधित है। हालांकि यह सच है कि मुझे केवल सरणी के अतीत का पता है, यह डेटा तक नहीं पहुंच रहा है - यह "एंड" प्रदान करने के लिए है, उसी तरह से आप पॉइंटर्स को पुनरावृत्तियों के रूप में उपयोग कर सकते हैं, जिसमें "अंत" एक अतीत है। अंतिम तत्व।
icabod

आप प्रभावी रूप से इसके लिए पहुंच B[0][0][i]रहे हैं i >= 3; इसकी अनुमति नहीं है क्योंकि यह (इनर) सरणी के बाहर पहुंच रहा है।
पारितंत्र

1
समाप्ति का आईएमओ का एक स्पष्ट तरीका यदि आप ऐसा करने वाले थे तो यह अंत है = प्रारंभ + (xSize * ySize * zSize)
noggin182

2

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

double B[10][8][5];
int index = 0;

while (index < (10 * 8 * 5))
{
    const int x = index % 10,
              y = (index / 10) % 10,
              z = index / 100;

    do_something_on_B(B[x][y][z]);
    ++index;
}

खैर, यह दृष्टिकोण सुरुचिपूर्ण और लचीला नहीं है, इसलिए हम सभी प्रक्रिया को एक टेम्पलेट फ़ंक्शन में पैक कर सकते हैं:

template <typename F, typename T, int X, int Y, int Z>
void iterate_all(T (&xyz)[X][Y][Z], F func)
{
    const int limit = X * Y * Z;
    int index = 0;

    while (index < limit)
    {
        const int x = index % X,
                  y = (index / X) % Y,
                  z = index / (X * Y);

        func(xyz[x][y][z]);
        ++index;
    }
}

यह टेम्पलेट फ़ंक्शन नेस्टेड लूप के रूप में भी व्यक्त किया जा सकता है:

template <typename F, typename T, int X, int Y, int Z>
void iterate_all(T (&xyz)[X][Y][Z], F func)
{
    for (auto &yz : xyz)
    {
        for (auto &z : yz)
        {
            for (auto &v : z)
            {
                func(v);
            }
        }
    }
}

और मनमाने आकार के 3 डी सरणी और फ़ंक्शन नाम को प्रदान करने के लिए उपयोग किया जा सकता है, जिससे पैरामीटर कटौती प्रत्येक आयाम के आकार को गिनने की कड़ी मेहनत करती है:

int main()
{
    int A[10][8][5] = {{{0, 1}, {2, 3}}, {{4, 5}, {6, 7}}};
    int B[7][99][8] = {{{0, 1}, {2, 3}}, {{4, 5}, {6, 7}}};

    iterate_all(A, do_something_on_A);
    iterate_all(B, do_something_on_B);

    return 0;
}

अधिक सामान्य की ओर

लेकिन एक बार फिर, इसमें लचीलेपन की कमी होती है, क्योंकि यह केवल 3D सरणियों के लिए काम करता है, लेकिन SFINAE का उपयोग करके हम एक मनमाना आयाम के सरणियों के लिए कार्य कर सकते हैं, पहले हमें एक टेम्पलेट फ़ंक्शन की आवश्यकता होती है जो रैंक 1 के सरणियों को पुन : प्रदर्शित करता है:

template<typename F, typename A>
typename std::enable_if< std::rank<A>::value == 1 >::type
iterate_all(A &xyz, F func)
{
    for (auto &v : xyz)
    {
        func(v);
    }
}

और एक अन्य जो पुनरावृत्ति कर किसी भी रैंक के सरणियों को पुनरावृत्त करता है:

template<typename F, typename A>
typename std::enable_if< std::rank<A>::value != 1 >::type
iterate_all(A &xyz, F func)
{
    for (auto &v : xyz)
    {
        iterate_all(v, func);
    }
}

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


के साथ काम करना std::vector

कई नेस्टेड वेक्टर के लिए, समाधान मनमाने ढंग से आयामों के मनमाने आकार के सरणी में से एक को पुन: बनाता है, लेकिन SFINAE के बिना: सबसे पहले हमें एक टेम्पलेट फ़ंक्शन की आवश्यकता होगी जो std::vectors को पुन: बनाता है और वांछित फ़ंक्शन को कॉल करता है:

template <typename F, typename T, template<typename, typename> class V>
void iterate_all(V<T, std::allocator<T>> &xyz, F func)
{
    for (auto &v : xyz)
    {
        func(v);
    }
}

और एक अन्य टेम्पलेट फ़ंक्शन जो वैक्टर के किसी भी प्रकार के वेक्टर को पुन: प्रसारित करता है और खुद को कॉल करता है:

template <typename F, typename T, template<typename, typename> class V> 
void iterate_all(V<V<T, std::allocator<T>>, std::allocator<V<T, std::allocator<T>>>> &xyz, F func)
{
    for (auto &v : xyz)
    {
        iterate_all(v, func);
    }
}

नेस्टिंग स्तर के बावजूद, iterate_allवेक्टर-ऑफ-वैक्टर संस्करण को कॉल करेगा , जब तक कि वेक्टर-ऑफ-वैल्यू संस्करण एक बेहतर मैच नहीं होगा, इस प्रकार पुनरावृत्ति को समाप्त करेगा।

int main()
{
    using V0 = std::vector< std::vector< std::vector<int> > >;
    using V1 = std::vector< std::vector< std::vector< std::vector< std::vector<int> > > > >;

    V0 A0 =   {{{0, 1}, {2, 3}}, {{4, 5}, {6, 7}}};
    V1 A1 = {{{{{9, 8}, {7, 6}}, {{5, 4}, {3, 2}}}}};

    iterate_all(A0, do_something_on_A);
    iterate_all(A1, do_something_on_A);

    return 0;
}

मुझे लगता है कि फ़ंक्शन बॉडी बहुत सरल और सीधे-आगे है ... मुझे आश्चर्य है कि यदि कंपाइलर इस लूप को अनियंत्रित कर सकता है (मुझे लगभग यकीन है कि अधिकांश कंपाइलर पहले उदाहरण को अनियंत्रित कर सकते हैं)।

देखें लाइव डेमो यहाँ

आशा करता हूँ की ये काम करेगा।


1

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

doOn( structure A, operator o)
{
    for (int k=0; k<A.size(); k++)
    {
            for (int i=0; i<A[k].size(); i++)
            {
                for (int j=0; j<A[k][i].size(); j++)
                {
                        o.actOn(A[k][i][j]);
                }
            }
    }
}

doOn(a, function12)
doOn(a, function13)

1

छोरों के लिए घोंसले के साथ छड़ी!

यहां बताई गई सभी विधियों में पठनीयता या लचीलेपन के मामले में नुकसान हैं।

यदि आपको बाहरी लूप में प्रसंस्करण के लिए एक आंतरिक लूप के परिणामों का उपयोग करने की आवश्यकता है तो क्या होगा? यदि आपको अपने आंतरिक लूप के भीतर बाहरी लूप से एक मूल्य की आवश्यकता होती है तो क्या होता है? अधिकांश "एन्कैप्सुलेशन" विधियाँ यहाँ विफल रहती हैं।

मुझे विश्वास करो कि मैंने छोरों के लिए नेस्टेड "क्लीन अप" करने के कई प्रयास देखे हैं और अंत में यह पता चला है कि नेस्टेड लूप वास्तव में सबसे साफ और सबसे लचीला समाधान है।


0

मैंने जिस तकनीक का उपयोग किया है वह टेम्प्लेट है। उदाहरण के लिए:

template<typename T> void do_something_on_A(std::vector<T> &vec) {
    for (auto& i : vec) { // can use a simple for loop in C++03
        do_something_on_A(i);
    }
}

void do_something_on_A(int &val) {
    // this is where your `do_something_on_A` method goes
}

फिर आप बस do_something_on_A(A)अपने मुख्य कोड में कॉल करें । टेम्पलेट फ़ंक्शन प्रत्येक आयाम के लिए एक बार बनाया जाता है, पहली बार के साथ T = std::vector<std::vector<int>>, दूसरी बार के साथ T = std::vector<int>

std::functionयदि आप चाहें तो आप इसे और अधिक सामान्य (या C ++ 03 में फ़ंक्शन-जैसे ऑब्जेक्ट) का उपयोग करके सामान्य बना सकते हैं:

template<typename T> void do_something_on_vec(std::vector<T> &vec, std::function &func) {
    for (auto& i : vec) { // can use a simple for loop in C++03
        do_something_on_vec(i, func);
    }
}

template<typename T> void do_something_on_vec(T &val, std::function &func) {
    func(val);
}

फिर इसे कॉल करें:

do_something_on_vec(A, std::function(do_something_on_A));

यह कार्य भले ही एक ही हस्ताक्षर हो, क्योंकि पहला फ़ंक्शन std::vectorप्रकार में किसी भी चीज़ के लिए बेहतर मेल है ।


0

आप इस तरह से एक लूप में सूचकांक उत्पन्न कर सकते हैं (ए, बी, सी आयाम हैं):

int A = 4, B = 3, C = 3;
for(int i=0; i<A*B*C; ++i)
{
    int a = i/(B*C);
    int b = (i-((B*C)*(i/(B*C))))/C;
    int c = i%C;
}

मैं आपसे सहमत हूं, यह विशेष रूप से 3 आयामों के लिए डिज़ाइन किया गया है;)
जनेक

1
इसका उल्लेख नहीं करना अविश्वसनीय रूप से धीमा है!
noggin182

@ noggin182: सवाल गति के बारे में नहीं था लेकिन नेस्टेड फॉर-लूप्स से बचने के बारे में था; इसके अलावा, वहाँ अनावश्यक विभाजन हैं, i / (B * C) को एक स्थान पर
लाया

ठीक है, यह एक वैकल्पिक तरीका है, शायद अधिक कुशल (जावास्क्रिप्ट): (var i = 0, j = 0, k = 0; i <A; i + = (j == B-1 && k == C -) 1)? 1: 0, j = (k == C - 1)? ((J == B-1)? 0: j + 1): j, k = (k == C - 1)? 0:? k + 1) {कंसोल.लॉग (i + "" + j + "" + k); }
जनेक

0

एक चीज जिसे आप आज़माना चाहते हैं, यदि आपके पास केवल आंतरिक-सबसे अधिक लूप में बयान हैं - और आपकी चिंता कोड की अत्यधिक क्रिया प्रकृति के बारे में अधिक है - एक अलग व्हाट्सएप योजना का उपयोग करना है। यह केवल तभी काम करेगा जब आप अपने छोरों के लिए कॉम्पैक्ट रूप से पर्याप्त रूप से बता सकते हैं ताकि वे सभी एक पंक्ति में फिट हों।

आपके पहले उदाहरण के लिए, मैं इसे फिर से लिखूंगा:

vector< vector< vector<int> > > A;
int i,j,k;
for(k=0;k<A.size();k++) for(i=0;i<A[k].size();i++) for(j=0;j<A[k][i].size();j++) {
    do_something_on_A(A[k][i][j]);
}

यह थोथा इसे आगे बढ़ा रहा है क्योंकि आप बाहरी छोरों में फ़ंक्शन को कॉल कर रहे हैं जो उनमें स्टेटमेंट डालने के बराबर है। मैंने सभी अनावश्यक व्हाइट-स्पेस को हटा दिया है और यह निष्क्रिय हो सकता है।

दूसरा उदाहरण बहुत बेहतर है:

double B[10][8][5];
int i,j,k;

for(k=0;k<10;k++) for(i=0;i<8;i++) for(j=0;j<5;j++) {
    do_something_on_B(B[k][i][j]);
}

यह आपके द्वारा उपयोग किए जाने की तुलना में अलग-अलग व्हाट्सएप सम्मेलन हो सकता है, लेकिन यह एक कॉम्पैक्ट परिणाम प्राप्त करता है कि फिर भी सी / सी ++ (जैसे मैक्रो सम्मेलनों) से परे किसी भी ज्ञान की आवश्यकता नहीं है और मैक्रोज़ जैसी किसी भी प्रवंचना की आवश्यकता नहीं है।

यदि आप वास्तव में एक मैक्रो चाहते हैं, तो आप इसे कुछ इस तरह से आगे ले जा सकते हैं:

#define FOR3(a,b,c,d,e,f,g,h,i) for(a;b;c) for(d;e;f) for(g;h;i)

जो दूसरा उदाहरण बदल जाएगा:

double B[10][8][5];
int i,j,k;

FOR3(k=0,k<10,k++,i=0,i<8,i++,j=0,j<5,j++) {
    do_something_on_B(B[k][i][j]);
}

और पहला उदाहरण किराए में भी बेहतर है:

vector< vector< vector<int> > > A;
int i,j,k;
FOR3(k=0,k<A.size(),k++,i=0,i<A[k].size(),i++,j=0,j<A[k][i].size(),j++) {
    do_something_on_A(A[k][i][j]);
}

उम्मीद है कि आप काफी आसानी से बता सकते हैं कि कौन से बयान किस बयान के लिए जाते हैं। इसके अलावा, अल्पविराम से सावधान रहें, अब आप उनमें से किसी भी एक खंड में उपयोग नहीं कर सकते for


1
इनकी पठनीयता भयानक है। forएक पंक्ति में एक से अधिक लूप जमा करने से यह अधिक पठनीय नहीं बनता है और यह इसे कम बनाता है ।

0

यहां एक C ++ 11 कार्यान्वयन है जो सब कुछ चलने योग्य बनाता है। अन्य समाधान खुद को ::iteratorटाइपडेफ़ या सरणियों के साथ कंटेनरों तक सीमित रखते हैं : लेकिन एfor_each पुनरावृत्ति के बारे में है, कंटेनर नहीं है।

मैं SFINAE को एक ही स्थान पर अलग करता हूं is_iterable । प्रेषण (तत्वों और पुनरावृत्तियों के बीच) टैग प्रेषण के माध्यम से किया जाता है, जो मुझे लगता है कि एक स्पष्ट समाधान है।

कंटेनरों और तत्वों पर लागू होने वाले कार्य सभी पूर्ण रूप से अग्रेषित किए जाते हैं, जिससे दोनों constऔर गैर- constरेंज और फंक्शनलर्स तक पहुंच होती है।

#include <utility>
#include <iterator>

मैं जिस टेम्पलेट फंक्शन को लागू कर रहा हूं। सब कुछ एक विवरण नाम स्थान में जा सकता है:

template<typename C, typename F>
void for_each_flat( C&& c, F&& f );

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

template<typename C, typename F>
void for_each_flat_helper( C&& c, F&& f, std::true_type /*is_iterable*/ ) {
  for( auto&& x : std::forward<C>(c) )
    for_each_flat(std::forward<decltype(x)>(x), f);
}
template<typename D, typename F>
void for_each_flat_helper( D&& data, F&& f, std::false_type /*is_iterable*/ ) {
  std::forward<F>(f)(std::forward<D>(data));
}

यह लिखने के लिए आवश्यक कुछ बॉयलरप्लेट है is_iterable। मैं पर तर्क निर्भर देखने करते हैं beginऔर endएक विस्तार नाम स्थान में। यह अनुकरण करता है कि एक for( auto x : y )लूप कितना अच्छा करता है:

namespace adl_aux {
  using std::begin; using std::end;
  template<typename C> decltype( begin( std::declval<C>() ) ) adl_begin(C&&);
  template<typename C> decltype( end( std::declval<C>() ) ) adl_end(C&&);
}
using adl_aux::adl_begin;
using adl_aux::adl_end;

TypeSinkयदि कोड मान्य है परीक्षण करने के लिए उपयोगी है। आप TypeSink< decltype(कोड करते हैं ) >और यदि codeमान्य है, तो अभिव्यक्ति है void। यदि कोड मान्य नहीं है, तो SFINAE किक करता है और विशेषज्ञता अवरुद्ध हो जाती है:

template<typename> struct type_sink {typedef void type;};
template<typename T> using TypeSink = typename type_sink<T>::type;

template<typename T, typename=void>
struct is_iterable:std::false_type{};
template<typename T>
struct is_iterable<T, TypeSink< decltype( adl_begin( std::declval<T>() ) ) >>:std::true_type{};

मैं केवल परीक्षण करता हूं begin। एक adl_endपरीक्षण भी किया जा सकता था।

अंतिम कार्यान्वयन for_each_flatअत्यंत सरल होने के कारण:

template<typename C, typename F>
void for_each_flat( C&& c, F&& f ) {
  for_each_flat_helper( std::forward<C>(c), std::forward<F>(f), is_iterable<C>() );
}        

जीवंत उदाहरण

यह नीचे की ओर नीचे है: शीर्ष उत्तरों के लिए बेझिझक, जो ठोस हैं। मैं बस कुछ बेहतर तकनीकों का इस्तेमाल करना चाहता था!


-2

सबसे पहले, आपको वैक्टर के एक वेक्टर का उपयोग नहीं करना चाहिए। प्रत्येक वेक्टर को सन्निहित स्मृति की गारंटी दी जाती है, लेकिन वैक्टर के वेक्टर की "वैश्विक" मेमोरी (और शायद नहीं होगी) नहीं है। आपको सी-स्टाइल सरणियों के बजाय मानक पुस्तकालय प्रकार सरणी का उपयोग करना चाहिए।

using std::array;

array<array<array<double, 5>, 8>, 10> B;
for (int k=0; k<10; k++)
    for (int i=0; i<8; i++)
        for (int j=0; j<5; j++)
            do_something_on_B(B[k][i][j]);

// or, if you really don't like that, at least do this:

for (int k=0; k<10; k++) {
    for (int i=0; i<8; i++) {
        for (int j=0; j<5; j++) {
            do_something_on_B(B[k][i][j]);
        }
    }
}

हालांकि अभी तक बेहतर, आप एक सरल 3D मैट्रिक्स वर्ग को परिभाषित कर सकते हैं:

#include <stdexcept>
#include <array>

using std::size_t;

template <size_t M, size_t N, size_t P>
class matrix3d {
    static_assert(M > 0 && N > 0 && P > 0,
                  "Dimensions must be greater than 0.");
    std::array<std::array<std::array<double, P>, N>, M> contents;
public:
    double& at(size_t i, size_t j, size_t k)
    { 
        if (i >= M || j >= N || k >= P)
            throw out_of_range("Index out of range.");
        return contents[i][j][k];
    }
    double& operator(size_t i, size_t j, size_t k)
    {
        return contents[i][j][k];
    }
};

int main()
{
    matrix3d<10, 8, 5> B;
        for (int k=0; k<10; k++)
            for (int i=0; i<8; i++)
                for (int j=0; j<5; j++)
                    do_something_on_B(B(i,j,k));
    return 0;
}

आप आगे जा सकते हैं और इसे पूरी तरह से सही बना सकते हैं, मैट्रिक्स गुणा (उचित और तत्व-वार), वैक्टर द्वारा गुणा, आदि जोड़ सकते हैं। आप इसे विभिन्न प्रकारों के लिए भी सामान्य कर सकते हैं (यदि आप मुख्य रूप से डबल्स का उपयोग करते हैं तो मैं इसे टेम्पलेट बना दूँगा) ।

आप प्रॉक्सी ऑब्जेक्ट भी जोड़ सकते हैं ताकि आप B [i] या B [i] [j] कर सकें। वे वैक्टर (गणितीय अर्थ में) और डबल और संभावित रूप से भरे हुए मेट्रिसेस वापस कर सकते थे?

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