Voici un bref aperçu relativement minimal filter
fonction.
Elle prend un prédicat. Elle retourne un objet fonction qui prend un itérable.
Elle renvoie un itérable qui peut être utilisé dans une for(:)
boucle.
template<class It>
struct range_t {
It b, e;
It begin() const { return b; }
It end() const { return e; }
bool empty() const { return begin()==end(); }
};
template<class It>
range_t<It> range( It b, It e ) { return {std::move(b), std::move(e)}; }
template<class It, class F>
struct filter_helper:range_t<It> {
F f;
void advance() {
while(true) {
(range_t<It>&)*this = range( std::next(this->begin()), this->end() );
if (this->empty())
return;
if (f(*this->begin()))
return;
}
}
filter_helper(range_t<It> r, F fin):
range_t<It>(r), f(std::move(fin))
{
while(true)
{
if (this->empty()) return;
if (f(*this->begin())) return;
(range_t<It>&)*this = range( std::next(this->begin()), this->end() );
}
}
};
template<class It, class F>
struct filter_psuedo_iterator {
using iterator_category=std::input_iterator_tag;
filter_helper<It, F>* helper = nullptr;
bool m_is_end = true;
bool is_end() const {
return m_is_end || !helper || helper->empty();
}
void operator++() {
helper->advance();
}
typename std::iterator_traits<It>::reference
operator*() const {
return *(helper->begin());
}
It base() const {
if (!helper) return {};
if (is_end()) return helper->end();
return helper->begin();
}
friend bool operator==(filter_psuedo_iterator const& lhs, filter_psuedo_iterator const& rhs) {
if (lhs.is_end() && rhs.is_end()) return true;
if (lhs.is_end() || rhs.is_end()) return false;
return lhs.helper->begin() == rhs.helper->begin();
}
friend bool operator!=(filter_psuedo_iterator const& lhs, filter_psuedo_iterator const& rhs) {
return !(lhs==rhs);
}
};
template<class It, class F>
struct filter_range:
private filter_helper<It, F>,
range_t<filter_psuedo_iterator<It, F>>
{
using helper=filter_helper<It, F>;
using range=range_t<filter_psuedo_iterator<It, F>>;
using range::begin; using range::end; using range::empty;
filter_range( range_t<It> r, F f ):
helper{{r}, std::forward<F>(f)},
range{ {this, false}, {this, true} }
{}
};
template<class F>
auto filter( F&& f ) {
return [f=std::forward<F>(f)](auto&& r)
{
using std::begin; using std::end;
using iterator = decltype(begin(r));
return filter_range<iterator, std::decay_t<decltype(f)>>{
range(begin(r), end(r)), f
};
};
};
J'ai pris des raccourcis. Une vraie bibliothèque devrait faire de vrais itérateurs, pas les for(:)
- des pseudo-fascades qualifiantes que j'ai faites.
Au point d'utilisation, cela ressemble à ceci :
int main()
{
std::vector<int> test = {1,2,3,4,5};
for( auto i: filter([](auto x){return x%2;})( test ) )
std::cout << i << '\n';
}
ce qui est plutôt bien, et imprime
1
3
5
Exemple concret .
Il existe une proposition d'ajout au C++ appelée Rangesv3 qui fait ce genre de choses et plus encore. boost
dispose également de plages de filtrage et d'itérateurs. boost a également des aides qui permettent d'écrire ce qui précède beaucoup plus rapidement.
7 votes
En dehors des fonctionnalités de la bibliothèque standard C++, vous pouvez essayer
std::copy_if
mais les sélections ne sont pas paresseuses.0 votes
Si vous êtes d'accord pour faire une copie, vous devriez jeter un coup d'oeil à std::copy_if
14 votes
Vous pourriez être intéressé par gamme-v3 . Il devrait également arriver en C++ en tant que TS et, espérons-le, être normalisé dans une prochaine version.
3 votes
c++11
dispose d'expressions lambda, ce qui peut également vous intéresser.0 votes
Je vous suggère de changer le titre car les deux réponses utilisent les boucles
3 votes
Pourriez-vous préciser si vous êtes intéressé par les aspects de performance, ou uniquement par les aspects syntaxiques ?
6 votes
Pour autant que je sache, ce morceau de code C# ne compilera pas.
:
doit être remplacé parin
3 votes
Pour ajouter au commentaire de @NullException, l'autre extrait C# ne compile pas non plus car il y a intentionnellement pas de
ForEach
surIEnumerable
12 votes
Je ressens le besoin de souligner que le
if
à l'intérieur d'unfor
que vous mentionnez est non seulement à peu près équivalent fonctionnellement aux autres exemples, mais serait aussi probablement plus rapide dans de nombreux cas. De plus, pour quelqu'un qui prétend aimer le style fonctionnel, ce que vous préconisez semble aller à l'encontre du concept de pureté cher à la programmation fonctionnelle puisqueDoStuff
a clairement des effets secondaires.0 votes
Si vous faites ça de nombreuses fois avec les mêmes conditions de filtrage, je suggérerais d'utiliser quelque chose comme Boost multi_index qui peut maintenir plusieurs index sur les mêmes données. C'est comme si vous aviez votre propre base de données dans l'application.
0 votes
Si vous le pouvez, je suppose que vous pourriez trier vos données pour remplacer les for + if par une seule boucle for qui itère sur les individus qui respectent vos conditions (ou deux boucles dans le cas d'un else, et ainsi de suite).
60 votes
Je n'ai jamais vraiment compris pourquoi les gens pensent que combiner toute la logique sur une seule ligne le rend d'une manière ou d'une autre meilleur ou plus lisible. Votre extrait C++ tout en haut est de loin le plus lisible pour moi parmi toutes vos possibilités. Et comme l'efficacité ne sera pas modifiée, je ne comprends pas pourquoi vous préférez ne pas l'écrire, à moins que vous ne soyez payé au nombre de lignes de code que vous supprimez.
2 votes
@CodyGray Je suis d'accord. Je pense que la boucle for avec if interne est de loin la plus lisible. Je ne suis pas sûr de l'efficacité, sauf que c'est probablement très proche. Obtenir un sous-ensemble et ensuite itérer sur le sous-ensemble nécessite logiquement deux traversées au lieu d'une, et cela fait toujours le test pour chaque élément, donc je préfère le traverser une fois et montrer explicitement le test qui va se produire de toute façon pour chaque élément. C'est (à peine) plus efficace de cette façon, et beaucoup plus transparent.
10 votes
@CodyGray D'accord : c'est juste du sucre syntaxique. Et le titre de la question est trompeur, car c'est très différent. éviter ramification et cacher sous forme d'abstraction.
2 votes
@CodyGray : Aujourd'hui, l'efficacité ne change pas. Demain - peut-être. Si vous demandez un filtrage d'abord, puis une gamme pour cela, puis - je spécule ici - le filtrage pourrait s'exécutent en parallèle (selon votre bibliothèque) et pourrait être plus rapide (en fonction de votre architecture). Nous ne le savons pas, mais nous ne voulons pas l'exclure.
7 votes
CodyGray Un avantage d'utiliser des choses comme
map
/filter
pour itérer sur des collections est que vous connaître ce qu'ils vont faire. Une boucle générique peut faire toutes sortes de choses complexes et il est beaucoup plus facile d'introduire un bogue manuellement en écrivant le code de la boucle.for
boucle que d'écriremap doStuff collection
. Cela peut également aider le compilateur lors de l'optimisation. La même chose est (encore) plus vraie lorsque l'on écrit une fonction récursive à la main ou que l'on utilise quelque chose comme une fonctionfold
/reduce
à la place.2 votes
Je suis surpris que personne n'ait signalé que Boost fait cela : coliru.stacked-crooked.com/a/f2274ee265d19a1a
0 votes
Si le compilateur devient suffisamment intelligent pour effectuer les types d'optimisations étonnantes que tu imagines, @lorro, il devrait certainement être assez intelligent pour détecter les modèles communs et les transformer afin de pouvoir effectuer les mêmes optimisations. Ce qui rendra important d'écrire le code de la manière normale et évidente. En supposant bien sûr que tout de ces optimisations sont même possibles, puisqu'elles brisent très probablement les garanties d'ordonnancement fournies par la spécification du langage.
1 votes
@CodyGray : Je ne parle pas de votre compilateur, je parle de la bibliothèque qui fournit
filter()
. Nous sommes d'accord sur le fait que ce n'est pas trivial pour un compilateur (et qu'il n'est pas non plus trivial de savoir si cela doit être fait en premier lieu), cependant avecstd::async()
il est maintenant presque trivial d'écrire unefilter()
qui effectue le filtrage en arrière-plan (ce qui est clairement indiqué dans la documentation). Tant que le filtre et le corps de la boucle sont suffisamment longs et peuvent être exécutés en parallèle, cela peut accélérer considérablement votre code.1 votes
@CodyGray : ce n'est pas nécessairement une question de lignes de code, il s'agit aussi de savoir si vous préférez traiter les opérations sur une plage, ou si vous préférez boucler sur une plage en effectuant explicitement l'opération équivalente sur chaque élément. Même sans le conditionnel, et même avant les boucles for basées sur une plage, certaines personnes n'utiliseraient pas encore
std::for_each
si vous aviez un pistolet sur leur tête, sur la base du fait que la boucle est plus lisible pour eux dans tous les cas. Alors que d'autres personnes aimaientfor_each
tellement qu'ils l'ont normalisé ;-)1 votes
Comme le dit l'OP, le désir de supprimer l'explicite
if
découle d'un style fonctionnel, c.f. degoes.net/articles/destroy-all-ifs0 votes
Duplicata possible de Comment combiner une fonction et un prédicat dans for_each ?
0 votes
@CodyGray c'est parce que vous ne vous exprimez pas au bon niveau d'abstraction. fluentcpp.com/2019/02/07/…
0 votes
@JonathanWakely oui le mouvement anti-if est sarcastique mais il a une base solide de vérité. Et la métrique actuelle s'appelle
cyclomatic complexity
. La programmation fonctionnelle vous permet d'effectuer la vérification du code... du tout. La méthode de programmation de Turing préconisée par CodyGray se heurte très rapidement aux limites de la décidabilité.