Avec presque tout le code que j'écris, je suis souvent confronté à des problèmes de réduction d'ensembles sur des collections qui finissent par se retrouver avec des conditions naïves «si» à l'intérieur. Voici un exemple simple:
for(int i=0; i<myCollection.size(); i++)
{
if (myCollection[i] == SOMETHING)
{
DoStuff();
}
}
Avec les langages fonctionnels, je peux résoudre le problème en réduisant la collection à une autre collection (facilement) et ensuite effectuer toutes les opérations sur mon ensemble réduit. En pseudocode:
newCollection <- myCollection where <x=true
map DoStuff newCollection
Et dans d'autres variantes C, comme C #, je pourrais réduire avec une clause where comme
foreach (var x in myCollection.Where(c=> c == SOMETHING))
{
DoStuff();
}
Ou mieux (du moins à mes yeux)
myCollection.Where(c=>c == Something).ToList().ForEach(d=> DoStuff(d));
Certes, je fais beaucoup de mélange de paradigmes et de style subjectif / basé sur l'opinion, mais je ne peux m'empêcher de penser qu'il me manque quelque chose de vraiment fondamental qui pourrait me permettre d'utiliser cette technique préférée avec C ++. Quelqu'un pourrait-il m'éclairer?
std::copy_if
, mais les sélections ne sont pas paresseusesif
intérieur d'un quefor
vous mentionnez n'est pas seulement à peu près équivalent sur le plan fonctionnel aux autres exemples, mais qu'il serait probablement aussi plus rapide dans de nombreux cas. Aussi pour quelqu'un qui prétend aimer le style fonctionnel, ce que vous promouvez semble aller à l'encontre du concept de pureté très apprécié de la programmation fonctionnelle depuisDoStuff
il a clairement des effets secondaires.Réponses:
IMHO, il est plus simple et plus lisible d'utiliser une boucle for avec un if à l'intérieur. Cependant, si cela vous ennuie, vous pouvez en utiliser un
for_each_if
comme celui ci-dessous:Cas d'utilisation:
Démo en direct
la source
find_if
etfind
qu'ils travaillent sur des plages ou des paires de itérateurs. (Il existe quelques exceptions, telles quefor_each
etfor_each_n
). Le moyen d'éviter d'écrire de nouveaux algos pour chaque éternuement est d'utiliser différentes opérations avec les algos existants, par exemple au lieu d'for_each_if
intégrer la condition dans l'appelable passé àfor_each
, par exemplefor_each(first, last, [&](auto& x) { if (cond(x)) f(x); });
Boost fournit des plages qui peuvent être utilisées avec des plages basées sur. Les plages ont l'avantage de ne pas copier la structure de données sous-jacente, elles fournissent simplement une «vue» (c'est-à-dire
begin()
,end()
pour la plage etoperator++()
,operator==()
pour l'itérateur). Cela pourrait vous intéresser: http://www.boost.org/libs/range/doc/html/range/reference/adaptors/reference/filtered.htmlla source
is_even
=>condition
,input
=>myCollection
etc.filtered()
- cela dit, il est préférable d'utiliser une lib prise en charge que du code ad hoc.Au lieu de créer un nouvel algorithme, comme le fait la réponse acceptée, vous pouvez utiliser un algorithme existant avec une fonction qui applique la condition:
Ou si vous voulez vraiment un nouvel algorithme, au moins le réutiliser
for_each
au lieu de dupliquer la logique d'itération:la source
std::for-each(first, last, [&](auto& x) {if (p(x)) op(x); });
c'est totalement plus simple quefor (Iter x = first; x != last; x++) if (p(x)) op(x);}
?std::for_each(std::execution::par, first, last, ...);
est-il facile d'ajouter ces choses à une boucle manuscrite?L'idée d'éviter
construit comme un anti-modèle est trop large.
Il est tout à fait correct de traiter plusieurs éléments qui correspondent à une certaine expression à l'intérieur d'une boucle, et le code ne peut pas être beaucoup plus clair que cela. Si le traitement devient trop volumineux pour tenir à l'écran, c'est une bonne raison d'utiliser un sous-programme, mais le conditionnel est toujours mieux placé à l'intérieur de la boucle, c'est-à-dire
est largement préférable à
Cela devient un anti-modèle lorsqu'un seul élément correspondra, car il serait alors plus clair de rechercher d'abord l'élément et d'effectuer le traitement en dehors de la boucle.
en est un exemple extrême et évident. Plus subtil, et donc plus courant, est un modèle d'usine comme
C'est difficile à lire, car il n'est pas évident que le code du corps ne sera exécuté qu'une seule fois. Dans ce cas, il serait préférable de séparer la recherche:
Il y a toujours un
if
dans unfor
, mais à partir du contexte, il devient clair ce qu'il fait, il n'est pas nécessaire de changer ce code à moins que la recherche ne change (par exemple en amap
), et il est immédiatement clair que cecreate_object()
n'est appelé qu'une seule fois, car il est pas dans une boucle.la source
for( range ){ if( condition ){ action } }
-style facilite la lecture des choses un morceau à la fois et n'utilise que la connaissance des constructions de base du langage.for(...) if(...) { ... }
c'est souvent le meilleur choix (c'est pourquoi j'ai qualifié la recommandation de diviser l'action en un sous-programme).for(…)if(…)…
si c'était le seul endroit où la recherche avait eu lieu.Voici une fonction rapide relativement minimale
filter
.Il faut un prédicat. Il renvoie un objet fonction qui prend un itérable.
Il renvoie un itérable qui peut être utilisé dans une
for(:)
boucle.J'ai pris des raccourcis. Une vraie bibliothèque devrait faire de vrais itérateurs, pas les
for(:)
pseudo-façades qualifiantes que j'ai faites.Au point d'utilisation, cela ressemble à ceci:
ce qui est plutôt sympa et imprime
Exemple en direct .
Il y a un ajout proposé à C ++ appelé Rangesv3 qui fait ce genre de chose et plus encore.
boost
a également des gammes de filtres / itérateurs disponibles. boost a également des aides qui rendent l'écriture de ce qui précède beaucoup plus courte.la source
Un style qui est assez utilisé pour être mentionné, mais qui n'a pas encore été mentionné, est:
Avantages:
DoStuff();
lorsque la complexité des conditions augmente. Logiquement,DoStuff();
devrait être au niveau supérieur de lafor
boucle, et c'est le cas.SOMETHING
s de la collection, sans exiger du lecteur qu'il vérifie qu'il n'y a rien après la fermeture}
duif
bloc.Désavantages:
continue
, Comme d' autres instructions de contrôle de flux, obtient mal utilisé dans les chemins qui mènent à coder en dur à suivre tant que certaines personnes sont opposés à toute utilisation d'entre eux: il y a un style valide de codage que certains suivi qui évitecontinue
, qui évitebreak
autre que en aswitch
, cela évitereturn
autrement qu'à la fin d'une fonction.la source
for
boucle qui s'étend sur de nombreuses lignes, un "sinon, continue" sur deux lignes est beaucoup plus clair, logique et lisible. Dire immédiatement «sauter ceci si» après que l'for
instruction se lit bien et, comme vous l'avez dit, n'indente pas les aspects fonctionnels restants de la boucle. Si lecontinue
est plus bas, cependant, une certaine clarté est sacrifiée (c'est-à-dire si une opération sera toujours effectuée avant l'if
instruction).Cela ressemble beaucoup à une
for
compréhension spécifique au C ++ . À toi?la source
auto const
n'a aucune incidence sur l'ordre d'itération. Si vous recherchez à distancefor
, vous verrez qu'il fait essentiellement une boucle standard debegin()
àend()
avec un déréférencement implicite. Il n'y a aucun moyen que cela puisse briser les garanties de commande (le cas échéant) du conteneur en cours d'itération; il aurait été ri de la face de la Terrestd::future
s,std::function
s, même ces fermetures anonymes sont très bien C ++ dans la syntaxe; chaque langue a son propre langage et, lorsqu'elle intègre de nouvelles fonctionnalités, elle essaie de les faire imiter l'ancienne syntaxe bien connue.Si DoStuff () dépendait d'une manière ou d'une autre de i dans le futur, je proposerais cette variante de masquage de bits sans branche garantie.
Où popcount est une fonction faisant un comptage de population (nombre de bits de comptage = 1). Il y aura une certaine liberté pour mettre des contraintes plus avancées avec i et leurs voisins. Si cela n'est pas nécessaire, nous pouvons dénuder la boucle intérieure et refaire la boucle extérieure
suivi d'un
la source
De plus, si vous ne vous souciez pas de réorganiser la collection, std :: partition est bon marché.
la source
std::partition
réorganise le conteneur.Je suis impressionné par la complexité des solutions ci-dessus. J'allais suggérer un simple
#define foreach(a,b,c,d) for(a; b; c)if(d)
mais il a quelques déficits évidents, par exemple, vous devez vous rappeler d'utiliser des virgules au lieu de points-virgules dans votre boucle, et vous ne pouvez pas utiliser l'opérateur virgule dansa
ouc
.la source
Une autre solution au cas où les i: s seraient importants. Celui-ci construit une liste qui remplit les index pour lesquels appeler doStuff (). Encore une fois, le point principal est d'éviter le branchement et de l'échanger contre des coûts arithmétiques pipelinables.
La ligne «magique» est la ligne de chargement de la mémoire tampon qui calcule arithmétiquement s'il faut garder la valeur et rester en position ou compter la position et ajouter de la valeur. Nous échangeons donc une branche potentielle contre certaines logiques et arithmétiques et peut-être quelques hits de cache. Un scénario typique dans lequel cela serait utile est si doStuff () effectue une petite quantité de calculs pipelinables et que toute branche entre les appels pourrait interrompre ces pipelines.
Ensuite, faites une boucle sur le tampon et exécutez doStuff () jusqu'à ce que nous atteignions cnt. Cette fois, nous aurons le courant i stocké dans le tampon afin que nous puissions l'utiliser dans l'appel à doStuff () si nous en avons besoin.
la source
On peut décrire votre modèle de code comme appliquant une fonction à un sous-ensemble d'une plage, ou en d'autres termes: l'appliquer au résultat de l'application d'un filtre à l'ensemble de la plage.
Ceci est réalisable de la manière la plus simple avec la bibliothèque range-v3 d'Eric Neibler ; même si c'est un peu une horreur, parce que vous voulez travailler avec des indices:
Mais si vous êtes prêt à renoncer aux indices, vous obtiendrez:
ce qui est plus agréable à mon humble avis.
PS - La bibliothèque de plages entre principalement dans le standard C ++ en C ++ 20.
la source
Je mentionnerai juste Mike Acton, il dirait certainement:
Si vous devez le faire, vous avez un problème avec vos données. Triez vos données!
la source