Для массива с несколькими измерениями нам обычно нужно написать 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
циклов, чтобы мне не приходилось каждый раз переписывать такой код? Есть лучший способ сделать это?
O(n) = n^3
код ...Ответы:
Во-первых, вы не используете такую структуру данных. Если вам нужна трехмерная матрица, вы определяете ее:
Или, если вы хотите индексировать с помощью
[][][]
, вам понадобится файл,operator[]
который возвращает прокси.После того, как вы это сделаете, если вы обнаружите, что вам постоянно нужно повторять, как вы представили, вы открываете итератор, который будет его поддерживать:
Тогда вы просто напишите:
(или просто:
если у вас C ++ 11.)
И если вам понадобятся три индекса во время таких итераций, можно создать итератор, который их предоставляет:
источник
vector<vector<vector<double> > >
для представления трехмерного поля. Переписывание кода, эквивалентного приведенному выше решению, привело к ускорению на 10.Matrix3D
вероятно , это должен быть шаблон, но это очень простой шаблон.) И вам нужно только отладитьMatrix3D
, а не каждый раз, когда вам понадобится 3D-матрица, так что вы сэкономите огромное количество времени на отладке. Насчет ясности:std::vector<std::vector<std::vector<int>>>
чем четчеMatrix3D
? Не говоря уже оMatrix3D
том, что это обеспечивает тот факт, что у вас есть матрица, в то время как вложенные векторы могут быть рваными, и что приведенное выше, вероятно, значительно быстрее.Использование макроса для скрытия
for
циклов может сбивать с толку, просто чтобы сэкономить несколько символов. Вместо этого я бы использовал циклы range-for :Конечно, вы можете заменить
auto&
на,const auto&
если фактически не изменяете данные.источник
int
переменные.do_something_on_A(*j)
?auto
fork
иi
может быть оправдано. За исключением того, что он все еще решает проблему на неправильном уровне; настоящая проблема в том, что он использует вложенные векторы.)k
- это целый вектор векторов (ну, ссылка на него), а не индекс.Что-то вроде этого может помочь:
Чтобы сделать это N-арным, нам понадобится некоторая магия шаблонов. Прежде всего мы должны создать структуру SFINAE, чтобы различать, это значение или контейнер. Реализация по умолчанию для значений и специализации для массивов и каждого типа контейнеров. Как отмечает @Zeta, мы можем определять стандартные контейнеры по вложенному
iterator
типу (в идеале мы должны проверить, можно ли использовать этот тип с базой диапазонаfor
или нет).Реализация
for_each
проста. Функция по умолчанию вызоветfunction
:И специализация вызовет себя рекурсивно:
И вуаля:
Также это не будет работать для указателей (массивов, размещенных в куче).
источник
Container
для других и для других.is_container : has_iterator<T>::value
из моего ответа, и вам не нужно писать специализацию для каждого типа, поскольку каждый контейнер должен иметьiterator
typedef. Не стесняйтесь полностью использовать что-либо из моего ответа, ваш уже лучше.Container
, поможет концепция.::iterator
не делает итеративный диапазон.int x[2][3][4]
идеально повторяется, так какstruct foo { int x[3]; int* begin() { return x; } int* end() { return x+3; } };
я не уверен, чтоT[]
должна делать специализация?Большинство ответов просто демонстрируют, как C ++ можно превратить в непонятные синтаксические расширения, IMHO.
Определяя любые шаблоны или макросы, вы просто заставляете других программистов понимать фрагменты обфусцированного кода, предназначенные для сокрытия других фрагментов запутанного кода.
Вы заставите каждого, кто читает ваш код, иметь опыт работы с шаблонами, просто чтобы не выполнять свою работу по определению объектов с четкой семантикой.
Если вы решили использовать необработанные данные, такие как трехмерные массивы, просто живите с ними или же определите класс, который придает вашим данным некоторый понятный смысл.
просто согласуется с загадочным определением вектора вектора вектора вектора int без явной семантики.
источник
ОБНОВЛЕНИЕ: Я знаю, что вы просили об этом, но лучше не используйте это :)
источник
TRIPLE_FOR
были определены в каком-то заголовке, что мне думать, когда я вижу здесь `TRIPLE_FOR.Одна из идей - написать повторяющийся псевдоконтейнерный класс, который «содержит» набор всех многоиндексных кортежей, которые вы будете индексировать. Здесь нет реализации, потому что это займет слишком много времени, но идея в том, что вы должны уметь писать ...
источник
Я вижу здесь много ответов, которые работают рекурсивно, определяя, является ли ввод контейнером или нет. Вместо этого, почему бы не определить, является ли текущий слой тем же типом, что и функция? Это намного проще и позволяет использовать более мощные функции:
Однако это (очевидно) дает нам ошибки двусмысленности. Поэтому мы используем SFINAE, чтобы определить, подходит ли текущий вход функции или нет.
Теперь это правильно обрабатывает контейнеры, но компилятор по-прежнему считает это неоднозначным для input_types, которые могут быть переданы функции. Таким образом, мы используем стандартный трюк C ++ 03, чтобы он предпочел первую функцию второй, также передавая ноль и делая одну, которую мы предпочитаем, accept и int, а другая принимает ...
Вот и все. Шесть относительно простых строк кода, и вы можете перебирать значения, строки или любую другую подгруппу, в отличие от всех других ответов.
Доказательство компиляции и исполнения здесь и здесь
Если вам нужен более удобный синтаксис в C ++ 11, вы можете добавить макрос. (Следующее не проверено)
источник
Я предупреждаю этот ответ следующим утверждением: это будет работать, только если вы работаете с реальным массивом - это не сработает для вашего примера с использованием
std::vector
.Если вы выполняете одну и ту же операцию с каждым элементом многомерного массива, не заботясь о положении каждого элемента, вы можете воспользоваться тем фактом, что массивы размещаются в смежных ячейках памяти, и рассматривать все это как одно целое. большой одномерный массив. Например, если мы хотим умножить каждый элемент на 2,0 во втором примере:
Обратите внимание, что использование вышеупомянутого подхода также позволяет использовать некоторые «правильные» методы C ++:
Я обычно не рекомендую этот подход (предпочитая что-то вроде ответа Джеффри), поскольку он полагается на определенные размеры для ваших массивов, но в некоторых случаях он может быть полезен.
источник
B[0][0][i]
кi >= 3
; это недопустимо, поскольку доступ осуществляется за пределами (внутреннего) массива.Я был шокирован тем, что никто не предложил какой-то цикл, основанный на арифметической магии, для выполнения этой работы.
Поскольку К. Ван ищет решение без вложенных циклов, я предлагаю одно:Что ж, этот подход не является элегантным и гибким, поэтому мы могли бы упаковать весь процесс в функцию-шаблон:
Эта функция-шаблон также может быть выражена в виде вложенных циклов:
И может использоваться, предоставляя трехмерный массив произвольного размера плюс имя функции, позволяя вычету параметра выполнять тяжелую работу по подсчету размера каждого измерения:
К более общему
Но опять же, ему не хватает гибкости, потому что он работает только для 3D-массивов, но с помощью SFINAE мы можем выполнять работу для массивов произвольного измерения, сначала нам нужна функция шаблона, которая выполняет итерацию массивов ранга 1:
И еще один, который выполняет итерацию массивов любого ранга, выполняя рекурсию:
Это позволяет нам перебирать все элементы во всех измерениях массива произвольных размеров произвольного размера.
Работаю с
std::vector
Для множественного вложенного вектора решение аналогично массиву произвольного размера, но без SFINAE: сначала нам понадобится функция-шаблон, которая выполняет итерацию
std::vector
s и вызывает желаемую функцию:И еще одна функция-шаблон, которая выполняет итерацию любого вектора векторов и вызывает себя:
Независимо от уровня вложенности,
iterate_all
будет вызывать версию вектора векторов, если версия вектора значений не является более подходящей, тем самым прекращая рекурсивность.Я думаю, что тело функции довольно простое и прямолинейное ... Интересно, сможет ли компилятор развернуть эти циклы (я почти уверен, что большинство компиляторов могли бы развернуть первый пример).
Смотрите живую демонстрацию здесь .
Надеюсь, поможет.
источник
Используйте что-нибудь в этом роде (его псевдокод, но идея остается той же). Вы извлекаете шаблон для цикла один раз и каждый раз применяете другую функцию.
источник
Придерживайтесь вложенных циклов for!
Все предложенные здесь методы имеют недостатки с точки зрения удобочитаемости или гибкости.
Что произойдет, если вам нужно использовать результаты внутреннего цикла для обработки во внешнем цикле? Что произойдет, если вам понадобится значение из внешнего цикла внутри вашего внутреннего цикла? Большинство методов "инкапсуляции" здесь терпят неудачу.
Поверьте, я видел несколько попыток «очистить» вложенные циклы for, и в итоге оказалось, что вложенный цикл на самом деле является самым чистым и гибким решением.
источник
Я использовал один из приемов - шаблоны. Например:
Затем вы просто вызываете
do_something_on_A(A)
свой основной код. Функция шаблона создается один раз для каждого измерения, первый раз сT = std::vector<std::vector<int>>
, второй раз сT = std::vector<int>
.Вы можете сделать это более универсальным, используя
std::function
(или функциональные объекты в C ++ 03) в качестве второго аргумента, если хотите:Тогда назовите это так:
Это работает, даже если функции имеют одинаковую сигнатуру, потому что первая функция лучше подходит для всего, что есть
std::vector
в типе.источник
Вы можете генерировать индексы в одном цикле следующим образом (A, B, C - размеры):
источник
Одна вещь, которую вы можете попробовать, если у вас есть только операторы в самом внутреннем цикле - и вас больше беспокоит чрезмерно многословный характер кода - это использовать другую схему пробелов. Это будет работать только в том случае, если вы можете указать свои циклы for достаточно компактно, чтобы все они умещались в одной строке.
Для вашего первого примера я бы переписал его так:
Это вроде как подталкивает, потому что вы вызываете функции во внешних циклах, что эквивалентно помещению в них операторов. Я удалил все ненужные пробелы, и это может быть приемлемо.
Второй пример намного лучше:
Это может быть другое соглашение о пробелах, чем вам нравится, но оно дает компактный результат, который, тем не менее, не требует каких-либо знаний, кроме C / C ++ (например, соглашения о макросах), и не требует каких-либо уловок, таких как макросы.
Если вам действительно нужен макрос, вы можете сделать еще один шаг, например:
что изменит второй пример на:
и первый пример тоже лучше:
Надеюсь, вы сможете довольно легко сказать, какие операторы сочетаются с какими операторами for. Также остерегайтесь запятых, теперь вы не можете использовать их в одном предложении любого из
for
s.источник
for
в строку более одного цикла, она не станет более читаемой, а станет меньше .Вот реализация C ++ 11, которая обрабатывает все итерации. Другие решения ограничиваются контейнерами с
::iterator
typedefs или массивами: но afor_each
- это итерация, а не контейнер.Я также выделяю SFINAE на одну точку в этой
is_iterable
характеристике. Диспетчеризация (между элементами и итерациями) осуществляется с помощью диспетчеризации тегов, что, на мой взгляд, является более ясным решением.Контейнеры и функции, применяемые к элементам, идеально перенаправляются, разрешая
const
и не имеяconst
доступа к диапазонам и функторам.Реализуемая мной функция шаблона. Все остальное может входить в пространство имен деталей:
Диспетчеризация тегов намного чище, чем в SFINAE. Эти два используются для итерируемых объектов и не повторяемых объектов соответственно. Последняя итерация первой могла бы использовать идеальную переадресацию, но я ленив:
Это некий шаблон, необходимый для написания
is_iterable
. Я делаю аргумент зависимый поиск наbegin
иend
в подробном пространстве имен. Это имитирует то, чтоfor( auto x : y )
цикл делает достаточно хорошо:Это
TypeSink
полезно для проверки правильности кода. Вы пишетеTypeSink< decltype(
код,) >
и еслиcode
верно, то выражение естьvoid
. Если код недействителен, включается SFINAE, и специализация блокируется:Я только проверяю
begin
. Такжеadl_end
можно провести тест.Окончательная реализация
for_each_flat
оказывается чрезвычайно простой:Живой пример
Это далеко внизу: не стесняйтесь переманивать верхние ответы, которые являются надежными. Я просто хотел использовать несколько лучших техник!
источник
Во-первых, нельзя использовать вектор векторов векторов. Каждый вектор гарантированно имеет непрерывную память, но «глобальная» память вектора векторов нет (и, вероятно, не будет). Вы также должны использовать массив стандартных библиотечных типов вместо массивов в стиле C.
А еще лучше, вы могли бы определить простой класс 3D-матрицы:
Вы можете пойти дальше и сделать его полностью корректным, добавить умножение матриц (собственное и поэлементное), умножение на векторы и т.д. Вы даже можете обобщить его на разные типы (я бы сделал его шаблоном, если вы в основном используете двойные) .
Вы также можете добавить прокси-объекты, чтобы вы могли делать B [i] или B [i] [j]. Они могут возвращать векторы (в математическом смысле) и матрицы, полные двойных &, потенциально?
источник