Почти со всем кодом, который я пишу, я часто сталкиваюсь с проблемами сокращения наборов в коллекциях, которые в конечном итоге заканчиваются наивными условиями «если» внутри них. Вот простой пример:
for(int i=0; i<myCollection.size(); i++)
{
if (myCollection[i] == SOMETHING)
{
DoStuff();
}
}
С функциональными языками я могу решить проблему, уменьшив коллекцию до другой коллекции (легко), а затем выполнить все операции с моим сокращенным набором. В псевдокоде:
newCollection <- myCollection where <x=true
map DoStuff newCollection
И в других вариантах C, таких как C #, я мог бы сократить с помощью предложения where, например
foreach (var x in myCollection.Where(c=> c == SOMETHING))
{
DoStuff();
}
Или лучше (по крайней мере, на мой взгляд)
myCollection.Where(c=>c == Something).ToList().ForEach(d=> DoStuff(d));
По общему признанию, я много смешиваю парадигмы и стиль, основанный на субъективном мнении, но я не могу не чувствовать, что мне не хватает чего-то действительно фундаментального, что позволило бы мне использовать этот предпочтительный метод с C ++. Может ли кто-нибудь просветить меня?
std::copy_if
, но выбор не ленивif
внутренняя часть, которуюfor
вы упомянули, не только в значительной степени функционально эквивалентна другим примерам, но также, вероятно, во многих случаях будет быстрее. Также для тех, кто утверждает, что любит функциональный стиль, то, что вы продвигаете, кажется, идет вразрез с очень любимой концепцией чистоты функционального программирования, посколькуDoStuff
явно имеет побочные эффекты.Ответы:
ИМХО более прямолинейно и читабельно использовать цикл for с if внутри него. Однако, если вас это раздражает, вы можете использовать такой,
for_each_if
как показано ниже:Вариант использования:
Живая демонстрация
источник
find_if
иfind
работают ли они на полигонах или пары итераторов. (Есть несколько исключений, например,for_each
иfor_each_n
). Способ избежать написания новых алгоритмов для каждого чиха - использовать разные операции с существующими алгоритмами, например, вместо того, чтобыfor_each_if
встраивать условие в вызываемый объект, переданныйfor_each
, например,for_each(first, last, [&](auto& x) { if (cond(x)) f(x); });
Boost предоставляет диапазоны, которые можно использовать для. Диапазоны имеют то преимущество , что они не копируют , лежащие в основе структуры данных, они просто обеспечивают «вид» (то есть
begin()
,end()
для диапазона иoperator++()
,operator==()
для итератора). Это может вас заинтересовать: http://www.boost.org/libs/range/doc/html/range/reference/adaptors/reference/filtered.htmlисточник
is_even
=>condition
,input
=>myCollection
и т. Д.filtered()
для вас - при этом лучше использовать поддерживаемая библиотека, чем какой-то специальный код.Вместо создания нового алгоритма, как это делает принятый ответ, вы можете использовать существующий с функцией, которая применяет условие:
Или, если вам действительно нужен новый алгоритм, по крайней мере, используйте его повторно
for_each
вместо дублирования логики итерации:источник
std::for-each(first, last, [&](auto& x) {if (p(x)) op(x); });
все проще, чемfor (Iter x = first; x != last; x++) if (p(x)) op(x);}
?std::for_each(std::execution::par, first, last, ...);
насколько легко добавить эти вещи в рукописный цикл?Идея избежать
конструкции как антипаттерн слишком широки.
Совершенно нормально обрабатывать несколько элементов, которые соответствуют определенному выражению изнутри цикла, и код не может быть намного яснее этого. Если обработка становится слишком большой и не помещается на экране, это хороший повод для использования подпрограммы, но все же условное выражение лучше всего помещать внутри цикла, т. Е.
намного предпочтительнее
Он становится антипаттерном, когда будет соответствовать только один элемент, потому что тогда будет проще сначала найти элемент и выполнить обработку вне цикла.
является крайним и очевидным примером этого. Более тонким и, следовательно, более распространенным является заводской шаблон, например
Это трудно прочитать, потому что не очевидно, что основной код будет выполнен только один раз. В этом случае лучше разделить поиск:
if
Внутри a все еще естьfor
, но из контекста становится ясно, что он делает, нет необходимости изменять этот код, если только поиск не изменится (например, на amap
), и сразу становится ясно, чтоcreate_object()
вызывается только один раз, потому что это не внутри петли.источник
for( range ){ if( condition ){ action } }
-стиль позволяет легко читать вещи по частям за раз и использует только знания основных языковых конструкций.for(...) if(...) { ... }
часто это лучший выбор (поэтому я уточнил рекомендацию разбить действие на подпрограмму).for(…)if(…)…
если бы это был единственный поиск места.Вот небольшая относительно минимальная
filter
функция.Требуется предикат. Он возвращает объект функции, который принимает итерацию.
Он возвращает итерацию, которую можно использовать в
for(:)
цикле.Я пошел короткими путями. Настоящая библиотека должна создавать настоящие итераторы, а не
for(:)
те псевдофасады, которые я делал.На момент использования это выглядит так:
что довольно приятно, и печатает
Живой пример .
Существует предлагаемое дополнение к C ++ под названием Rangesv3, которое делает такие вещи и многое другое.
boost
также доступны диапазоны фильтров / итераторы. У boost также есть помощники, которые делают написание вышеупомянутого намного короче.источник
Один стиль, который используется достаточно, чтобы упомянуть, но еще не упоминался, это:
Преимущества:
DoStuff();
при увеличении сложности условия. По логике, онDoStuff();
должен быть на верхнем уровнеfor
цикла, и это так.SOMETHING
с коллекцией, не требуя от читателя , чтобы убедиться , что нет ничего после закрытия}
этогоif
блока.Недостатки:
continue
, как и другие операторы управления потоком, неправильно используется таким образом, что приводит к настолько сложному для понимания коду, что некоторые люди выступают против их любого использования: существует допустимый стиль кодирования, которому некоторые следуют, который избегаетcontinue
, который избегает,break
кроме вswitch
, что позволяет избежать,return
кроме как в конце функции.источник
for
цикле, состоящем из многих строк, двухстрочное «если нет, продолжить» намного яснее, логичнее и читабельнее. Сразу же скажите «пропустите это, если» после того, какfor
оператор будет хорошо читаться и, как вы сказали, не отступает от остальных функциональных аспектов цикла. Однако, если значениеcontinue
находится ниже, некоторая ясность принесена в жертву (т.е. если какая-то операция всегда будет выполняться передif
оператором).for
Для меня это очень похоже на понимание C ++ . Тебе?источник
auto const
имеет никакого отношения к порядку итераций. Если вы посмотрите на ранжированныйfor
, вы увидите, что он в основном выполняет стандартный цикл отbegin()
доend()
с неявным разыменованием. Невозможно нарушить гарантии упорядочивания (если таковые имеются) контейнера, по которому выполняется итерация; над этим бы посмеялись с лица Землиstd::future
s,std::function
s, даже эти анонимные замыкания очень хорошо сочетаются с синтаксисом C ++; у каждого языка есть свой собственный язык, и при включении новых функций он пытается заставить их имитировать старый хорошо известный синтаксис.Если DoStuff () будет как-то зависеть от i в будущем, я бы предложил этот гарантированный вариант битовой маскировки без ветвлений.
Где popcount - это любая функция, выполняющая подсчет населения (количество бит = 1). Будет некоторая свобода устанавливать более сложные ограничения с i и их соседями. Если в этом нет необходимости, мы можем разделить внутренний цикл и переделать внешний цикл.
за которым следует
источник
Кроме того, если вам не нужно переупорядочивать коллекцию, std :: partition стоит недорого.
источник
std::partition
переупорядочивает контейнер.Я в восторге от сложности вышеуказанных решений. Я собирался предложить простой,
#define foreach(a,b,c,d) for(a; b; c)if(d)
но у него есть несколько очевидных недостатков, например, вы должны помнить, что в вашем цикле нужно использовать запятые вместо точки с запятой, и вы не можете использовать оператор запятой вa
илиc
.источник
Другое решение в случае, если i: s важны. Он формирует список, который заполняет индексы, для которых вызывается doStuff (). Опять же, главное - избежать разветвления и обменять его на конвейерные арифметические затраты.
«Магическая» линия - это линия загрузки буфера, которая арифметически вычисляет, нужно ли сохранять значение и оставаться в позиции или подсчитывать позицию и добавлять значение. Поэтому мы меняем потенциальную ветвь на некоторую логику и арифметику и, возможно, на некоторые попадания в кеш. Типичный сценарий, когда это было бы полезно, - если doStuff () выполняет небольшой объем конвейерных вычислений, и любая ветвь между вызовами может прервать эти конвейеры.
Затем просто переберите буфер и запускайте doStuff (), пока не дойдете до cnt. На этот раз у нас будет текущий i, сохраненный в буфере, поэтому мы можем использовать его в вызове doStuff (), если нам понадобится.
источник
Можно описать ваш шаблон кода как применение некоторой функции к подмножеству диапазона, или, другими словами: применение его к результату применения фильтра ко всему диапазону.
Этого можно достичь самым простым способом с помощью библиотеки Эрика Нейблера range -v3 ; хотя это немного раздражает, потому что вы хотите работать с индексами:
Но если вы готовы отказаться от индексов, вы получите:
что приятнее ИМХО.
PS - Библиотека диапазонов в основном входит в стандарт C ++ в C ++ 20.
источник
Упомяну только Майка Эктона, он обязательно скажет:
Если вам нужно это сделать, у вас проблемы с вашими данными. Сортируйте свои данные!
источник