Чистые способы написания нескольких циклов for

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
Когда у вас есть вектор вектора вектора, это признак плохого дизайна.
Maroun
5
@Nim: Вы можете сделать это с 1 плоским массивом (не уверен, что это лучше).
Jarod42 08
16
Я думаю, вы не захотите скрывать потенциальный O(n) = n^3код ...
poy
36
@ TC1: И тогда мне будет труднее читать. Это все вопрос личных предпочтений, и на самом деле это не помогает решить поставленный здесь вопрос.
ereOn 08

Ответы:

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
Этот ответ должен получить больше голосов, поскольку он единственный, который касается фактического источника проблемы.
ereOn
5
это может быть правильный ответ, но я не согласен, что это хороший ответ. множество загадочных кодов шаблонов с, вероятно, в 10 раз медленным временем компиляции и, вероятно, в 10 раз медленным кодом отладки (может быть больше). Для меня определенно оригинальный код для меня более понятен ...
Gorkem
10
@beehorf ... и намного медленнее. Потому что многомерные массивы в C и C ++ на самом деле являются вложенными массивами в том смысле, что внешние измерения хранят указатели на вложенные массивы. Эти вложенные массивы затем произвольно разбросаны по памяти, эффективно подавляя любую предварительную выборку и кеширование. Я знаю примеры, когда кто-то писал код, используя vector<vector<vector<double> > >для представления трехмерного поля. Переписывание кода, эквивалентного приведенному выше решению, привело к ускорению на 10.
Майкл Уайлд,
5
@beehorf Где вы видите код шаблона? (На практике, Matrix3Dвероятно , это должен быть шаблон, но это очень простой шаблон.) И вам нужно только отладить Matrix3D, а не каждый раз, когда вам понадобится 3D-матрица, так что вы сэкономите огромное количество времени на отладке. Насчет ясности: std::vector<std::vector<std::vector<int>>>чем четче Matrix3D? Не говоря уже о Matrix3Dтом, что это обеспечивает тот факт, что у вас есть матрица, в то время как вложенные векторы могут быть рваными, и что приведенное выше, вероятно, значительно быстрее.
Джеймс Канце
10
@MichaelWild Но, конечно, реальное преимущество моего подхода в том, что вы можете изменить представление в зависимости от того, что работает быстрее в вашей среде, без необходимости изменять какой-либо клиентский код. Ключ к хорошей производительности - это правильная инкапсуляция, так что вы можете вносить изменения, которые профилировщик сообщает вам, без необходимости переписывать все приложение.
Джеймс Канце
44

Использование макроса для скрытия forциклов может сбивать с толку, просто чтобы сэкономить несколько символов. Вместо этого я бы использовал циклы range-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.
Jarod42 08
1
@herohuyongtao В случае итераторов. Что может быть здесь более идиоматично, но есть случаи, когда вам нужны три intпеременные.
Джеймс Канце
1
А этого не должно быть do_something_on_A(*j)?
Джеймс Канце
1
@ Джефффри Ах да. Еще одна причина разобрать тип. (Я полагаю, что использование autofor kи iможет быть оправдано. За исключением того, что он все еще решает проблему на неправильном уровне; настоящая проблема в том, что он использует вложенные векторы.)
Джеймс Канз
2
@Dhara k- это целый вектор векторов (ну, ссылка на него), а не индекс.
Yakk - Адам Неврамонт 08
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; });
 }

Чтобы сделать это N-арным, нам понадобится некоторая магия шаблонов. Прежде всего мы должны создать структуру 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для других и для других.
задал запрос 08
1
@herohuyongtao Я сделал пример K-арного foreach.
задал вопрос
1
@fasked: используйте is_container : has_iterator<T>::valueиз моего ответа, и вам не нужно писать специализацию для каждого типа, поскольку каждый контейнер должен иметь iteratortypedef. Не стесняйтесь полностью использовать что-либо из моего ответа, ваш уже лучше.
Zeta
@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.

Определяя любые шаблоны или макросы, вы просто заставляете других программистов понимать фрагменты обфусцированного кода, предназначенные для сокрытия других фрагментов запутанного кода.
Вы заставите каждого, кто читает ваш код, иметь опыт работы с шаблонами, просто чтобы не выполнять свою работу по определению объектов с четкой семантикой.

Если вы решили использовать необработанные данные, такие как трехмерные массивы, просто живите с ними или же определите класс, который придает вашим данным некоторый понятный смысл.

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

просто согласуется с загадочным определением вектора вектора вектора вектора int без явной семантики.

курои неко
источник
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;
}

ОБНОВЛЕНИЕ: Я знаю, что вы просили об этом, но лучше не используйте это :)

FreeNickname
источник
5
Я знаю, что это то, о чем просил OP, но серьезно ... Это похоже на изумительный пример обфускации. Предположим, они TRIPLE_FORбыли определены в каком-то заголовке, что мне думать, когда я вижу здесь `TRIPLE_FOR.
Джеймс Канце
2
Да, думаю, ты прав :) Думаю, оставлю здесь просто как пример, что это можно сделать с помощью макроса, но добавлю примечание, что лучше так не делать :) Я только что проснулся и решил использовать этот вопрос как небольшую разминку для ума.
FreeNickname 08
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.
}
Стив314
источник
лучший ответ здесь имо.
davidhigh
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);}

Теперь это правильно обрабатывает контейнеры, но компилятор по-прежнему считает это неоднозначным для input_types, которые могут быть переданы функции. Таким образом, мы используем стандартный трюк C ++ 03, чтобы он предпочел первую функцию второй, также передавая ноль и делая одну, которую мы предпочитаем, accept и 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;
}

Обратите внимание, что использование вышеупомянутого подхода также позволяет использовать некоторые «правильные» методы C ++:

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 08
@ecatmur: Интересно - я только начал работать, поэтому проверю и обновлю / удалю ответ соответственно. Спасибо.
icabod
@ecatmur: Я посмотрел на стандарт C ++ 11 (раздел 8.3.4), и то, что я написал, должно работать и не выглядит незаконным (для меня). Ссылка, которую вы предоставили, относится к доступу к членам за пределами определенного размера массива. Хотя это правда, что я получаю адрес сразу за массивом, он не обращается к данным - это делается для того, чтобы обеспечить «конец», так же, как вы можете использовать указатели в качестве итераторов, причем «конец» - это один из прошедших последний элемент.
icabod
Вы фактически получаете доступ B[0][0][i]к i >= 3; это недопустимо, поскольку доступ осуществляется за пределами (внутреннего) массива.
ecatmur
1
Более четкий способ ИМО назначить конец, ЕСЛИ вы должны были это сделать, - это end = start + (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);
            }
        }
    }
}

И может использоваться, предоставляя трехмерный массив произвольного размера плюс имя функции, позволяя вычету параметра выполнять тяжелую работу по подсчету размера каждого измерения:

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;
}

Я думаю, что тело функции довольно простое и прямолинейное ... Интересно, сможет ли компилятор развернуть эти циклы (я почти уверен, что большинство компиляторов могли бы развернуть первый пример).

Смотрите живую демонстрацию здесь .

Надеюсь, поможет.

PaperBirdMaster
источник
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

Придерживайтесь вложенных циклов for!

Все предложенные здесь методы имеют недостатки с точки зрения удобочитаемости или гибкости.

Что произойдет, если вам нужно использовать результаты внутреннего цикла для обработки во внешнем цикле? Что произойдет, если вам понадобится значение из внешнего цикла внутри вашего внутреннего цикла? Большинство методов "инкапсуляции" здесь терпят неудачу.

Поверьте, я видел несколько попыток «очистить» вложенные циклы for, и в итоге оказалось, что вложенный цикл на самом деле является самым чистым и гибким решением.

Джеймс Андерсон
источник
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в типе.

JoshG79
источник
0

Вы можете генерировать индексы в одном цикле следующим образом (A, B, C - размеры):

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-х измерений;)
janek 08
1
Не говоря уже о том, что это невероятно медленно!
noggin182
@ noggin182: вопрос был не в скорости, а в том, чтобы избежать вложенных циклов for; к тому же там есть ненужные деления, i / (B * C) можно заменить на a
janek
Хорошо, это альтернативный способ, возможно, более эффективный (javascript): for (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: к + 1) {console.log (я + "" + j + "" + к); }
janek
0

Одна вещь, которую вы можете попробовать, если у вас есть только операторы в самом внутреннем цикле - и вас больше беспокоит чрезмерно многословный характер кода - это использовать другую схему пробелов. Это будет работать только в том случае, если вы можете указать свои циклы for достаточно компактно, чтобы все они умещались в одной строке.

Для вашего первого примера я бы переписал его так:

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]);
}

Это может быть другое соглашение о пробелах, чем вам нравится, но оно дает компактный результат, который, тем не менее, не требует каких-либо знаний, кроме C / C ++ (например, соглашения о макросах), и не требует каких-либо уловок, таких как макросы.

Если вам действительно нужен макрос, вы можете сделать еще один шаг, например:

#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. Также остерегайтесь запятых, теперь вы не можете использовать их в одном предложении любого из fors.

Майкл
источник
1
Их читаемость ужасна. Если вставить forв строку более одного цикла, она не станет более читаемой, а станет меньше .
0

Вот реализация C ++ 11, которая обрабатывает все итерации. Другие решения ограничиваются контейнерами с ::iteratortypedefs или массивами: но a 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

Во-первых, нельзя использовать вектор векторов векторов. Каждый вектор гарантированно имеет непрерывную память, но «глобальная» память вектора векторов нет (и, вероятно, не будет). Вы также должны использовать массив стандартных библиотечных типов вместо массивов в стиле C.

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]. Они могут возвращать векторы (в математическом смысле) и матрицы, полные двойных &, потенциально?

Miles Rout
источник