web-dev-qa-db-fra.com

Quel algorithme STL peut déterminer si exactement un élément d'un conteneur satisfait un prédicat?

J'ai besoin d'un algorithme STL qui prend un prédicat et une collection et retourne true si un et un seul membre de la collection satisfait le prédicat, sinon retourne false.

Comment pourrais-je faire cela en utilisant des algorithmes STL?

Par exemple, pour remplacer ce qui suit par du code d'algorithme STL pour exprimer la même valeur de retour.

int count = 0;

for( auto itr = c.begin(); itr != c.end(); ++itr ) {
    if ( predicate( *itr ) ) {
      if ( ++count > 1 ) {
        break;
      }
    }
}

return 1 == count;
46
WilliamKF

Deux choses me viennent à l'esprit:

std::count_if puis comparez le résultat à 1.

Pour éviter de traverser tout le conteneur au cas où, par exemple, les deux premiers éléments correspondent déjà au prédicat, j'utiliserais deux appels à la recherche d'éléments correspondants. Quelque chose le long de la ligne de

auto it = std::find_if(begin,end,predicate);
if (it == end) return false;
++it;
return std::none_of(it,end,predicate);

Ou si vous le préférez plus compact:

auto it = std::find_if(begin,end,predicate); 
return (it != end) && std::none_of(std::next(it),end,predicate);

Remerciements à Remy Lebeau pour le compactage, Deduplicator pour le débracketing et Blastfurnance pour se rendre compte que nous pouvons également utiliser none_of les algorithmes std.

78
idclev 463035818

Vous pouvez utiliser std::count_if compter et retourner s'il en est un.

Par exemple:

#include <iostream>
#include <algorithm> // std::count_if
#include <vector>    // std::vector
#include <ios>       // std::boolalpha

template<class Iterator, class UnaryPredicate>
constexpr bool is_count_one(Iterator begin, const Iterator end, UnaryPredicate pred)
{
    return std::count_if(begin, end, pred) == 1;
}

int main()
{
    std::vector<int> vec{ 2, 4, 3 };
    // true: if only one Odd element present in the container
    std::cout << std::boolalpha
              << is_count_one(vec.cbegin(), vec.cend(),
                  [](const int ele) constexpr noexcept -> bool { return ele & 1; });
    return 0;
}

Mise à jour : Cependant, std::count_if compte l'élément entier dans le conteneur, ce qui n'est pas bon comme l'algorithme donné dans la question. La meilleure approche utilisant les collections d'algorithmes standard a été mentionnée dans la réponse de @ formerlyknownas_463035818.

Cela étant dit, l'approche de OP est également bonne comme la meilleure approche standard mentionnée ci-dessus, où un court-circuit se produit lorsque count atteint 2. Si quelqu'un est intéressé par une fonction de modèle d'algorithme non standard pour l'approche OP, voici la réponse.

#include <iostream>
#include <vector>    // std::vector
#include <ios>       // std::boolalpha
#include <iterator>  // std::iterator_traits

template<class Iterator, class UnaryPredicate>
bool is_count_one(Iterator begin, const Iterator end, UnaryPredicate pred)
{
    typename std::iterator_traits<Iterator>::difference_type count{ 0 };
    for (; begin != end; ++begin) {
        if (pred(*begin) && ++count > 1) return false;
    }
    return count == 1;
}

int main()
{
    std::vector<int> vec{ 2, 3, 4, 2 };
    // true: if only one Odd element present in the container
    std::cout << std::boolalpha
              << is_count_one(vec.cbegin(), vec.cend(),
                  [](const int ele) constexpr noexcept -> bool { return ele & 1; });
    return 0;
}

Maintenant que cela peut être généralisé , en fournissant un paramètre de plus, le nombre de N élément (s) doit/doit être trouvé dans le conteneur.

template<typename Iterator>
using diff_type = typename std::iterator_traits<Iterator>::difference_type;

template<class Iterator, class UnaryPredicate>
bool has_exactly_n(Iterator begin, const Iterator end, UnaryPredicate pred, diff_type<Iterator> N = 1)
{
    diff_type<Iterator> count{ 0 };
    for (; begin != end; ++begin) {
        if (pred(*begin) && ++count > N) return false;
    }
    return count == N;
}
14
JeJo

À partir de anciennement la réponse de unknown_463035818 , cela peut être généralisé pour voir si un conteneur a exactement n éléments qui satisfont un prédicat. Pourquoi? Parce que c'est C++ et nous ne sommes pas satisfaits jusqu'à ce que nous puissions lire les e-mails au moment de la compilation.

template<typename Iterator, typename Predicate>
bool has_exactly_n(Iterator begin, Iterator end, size_t count, Predicate predicate)
{
    if(count == 0)
    {
        return std::none_of(begin, end, predicate);
    }
    else
    {
        auto iter = std::find_if(begin, end, predicate);
        return (iter != end) && has_exactly_n(std::next(iter), end, count - 1, predicate);
    }
}
11
Mark H

En utilisant std::not_fn pour annuler un prédicat

Comme le cœur de l'algorithme de cette question (comme cela a été élégamment couvert en combinant std::find_if et std::none_of in la réponse acceptée ), avec un court-circuit en cas d'échec, consiste à rechercher dans un conteneur un prédicat unaire et, lorsqu'il est rencontré, à continuer à rechercher dans le reste du conteneur la négation du prédicat, je mentionnerai aussi le négateur std::not_fn introduit en C++ 17, remplaçant le moins utile std::not1 et std::not2 constructions.

Nous pouvons utiliser std::not_fn pour implémenter la même logique de prédicat que la réponse acceptée (std::find_if conditionnellement suivi de std::none_of), mais avec une sémantique quelque peu différente, remplaçant cette dernière étape (std::none_of) avec std::all_of sur la négation du prédicat unaire utilisé dans la première étape (std::find_if). Par exemple.:

// C++17
#include <algorithm>   // std::find_if
#include <functional>  // std::not_fn
#include <ios>         // std::boolalpha
#include <iostream>
#include <iterator>  // std::next
#include <vector>

template <class InputIt, class UnaryPredicate>
constexpr bool one_of(InputIt first, InputIt last, UnaryPredicate p) {
  auto it = std::find_if(first, last, p);
  return (it != last) && std::all_of(std::next(it), last, std::not_fn(p));
}

int main() {
  const std::vector<int> v{1, 3, 5, 6, 7};
  std::cout << std::boolalpha << "Exactly one even number : "
            << one_of(v.begin(), v.end(), [](const int n) {
                 return n % 2 == 0;
               });  // Exactly one even number : true
}

Une approche pack de paramètres pour les conteneurs de taille statique

Comme j'ai déjà limité cette réponse au C++ 14 (et au-delà), j'inclurai une approche alternative pour les conteneurs de taille statique (ici appliqué pour std::array, en particulier), en utilisant std::index_sequence combiné avec l'extension du pack de paramètres:

#include <array>
#include <ios>         // std::boolalpha
#include <iostream>
#include <utility>     // std::(make_)index_sequence

namespace detail {
template <typename Array, typename UnaryPredicate, std::size_t... I>
bool one_of_impl(const Array& arr, const UnaryPredicate& p,
                 std::index_sequence<I...>) {
  bool found = false;
  auto keep_searching = [&](const int n){
      const bool p_res = found != p(n);
      found = found || p_res;
      return !found || p_res;
  };
  return (keep_searching(arr[I]) && ...) && found;
}
}  // namespace detail

template <typename T, typename UnaryPredicate, std::size_t N,
          typename Indices = std::make_index_sequence<N>>
auto one_of(const std::array<T, N>& arr,
            const UnaryPredicate& p) {
  return detail::one_of_impl(arr, p, Indices{});
}

int main() {
  const std::array<int, 5> a{1, 3, 5, 6, 7};
  std::cout << std::boolalpha << "Exactly one even number : "
            << one_of(a, [](const int n) {
                 return n % 2 == 0;
               });  // Exactly one even number : true
}

Cela court-circuitera également en cas d'échec précoce ("trouvé plus d'un"), mais contiendra quelques comparaisons booléennes plus simples que dans l'approche ci-dessus.

Cependant , notez que cette approche pourrait avoir ses inconvénients, en particulier pour le code optimisé pour les entrées de conteneur avec de nombreux éléments, comme le souligne @PeterCordes dans un commentaire ci-dessous. Citer le commentaire (car les commentaires ne sont pas garantis de persister dans le temps):

Ce n'est pas parce que la taille est statique que le déroulement complet de la boucle avec des modèles est une bonne idée. Dans l'asm résultant, cela a de toute façon besoin d'une branche à chaque itération pour s'arrêter sur found, de sorte qu'il pourrait aussi bien s'agir d'une branche de boucle. Les processeurs sont bons pour exécuter des boucles (caches de code, tampons de bouclage). Les compilateurs dérouleront entièrement les boucles de taille statique basées sur l'heuristique, mais probablement ne le fera pas restaurez cela si a est énorme. Donc votre premier one_of l'implémentation a déjà le meilleur des deux mondes, en supposant un compilateur moderne normal comme gcc ou clang, ou peut-être MSVC

8
dfri