35 votes

Astuces de comparaison en C ++

Une classe:

class foo{
public:
    int data;
};

Maintenant, je veux ajouter une méthode de cette classe, pour faire de la comparaison, pour voir si ses données est égale à l'un des nombres donnés.

Bien sûr, je peux écrire if(data==num1|| data == num2|| data ==num3.....), mais honnêtement parlant, je me sens malade quand j'écris data == chaque fois que je la compare à un nombre.

Donc, j'espère que je serais capable d'écrire quelque chose comme ceci:

if(data is equal to one of these(num1,num2,num3,num4,num5...))
    return true;
else
    return false;

Je veux mettre en œuvre cette déclaration, data is equal to one of these(num1, num2, num3, num4, num5...)

Voici ma démarche:

#include <stdarg.h>
bool is_equal_to_one_of_these(int count,...){
    int i;
    bool equal = false;
    va_list arg_ptr;
    va_start(arg_prt,count);
    for(int x=0;x<count;x++){
        i = va_arg(arg_ptr,int);
        if( i == data ){
            equal = true;
            break;
        }
    }
    va_end(arg_ptr);
    return equal;
}

Ce bout de code va faire le travail pour moi. Mais chaque fois que j'utilise cette méthode, je vais avoir à compter le nombre de paramètres et de le transmettre.

Quelqu'un aurait-il une meilleure idée?

46voto

TemplateRex Points 26447

La voie de la facilité

L'approche la plus simple est d'écrire une fonction membre wrapper appelés in() autour std::find avec une paire d'itérateurs pour chercher les données en question. J'ai écrit un simple template<class It> in(It first, It last) fonction de membre pour que

template<class It>
bool in(It first, It last) const
{
    return std::find(first, last, data) != last;
}

Si vous n'avez pas accès à la source de foo, vous pouvez écrire un non-fonctions de membre de la signature template<class T> bool in(foo const&, std::initializer_list<T>) etc., et de l'appeler comme les

in(f, {1, 2, 3 });

La dure

Mais nous laisser complètement aller à la mer avec ça: il suffit d'ajouter deux de plus public surcharges:

  • celui qui prend un std::initializer_list paramètre que l'on appelle la précédente, avec l' begin() et end() des itérateurs de la correspondante de la liste d'initialiseur argument.
  • l'un pour l'arbitraire d'un conteneur d'entrée qui permettra de faire un peu de balise d'envoi de deux plus private surcharges d'une detail_in()helper:
    • une surcharge de faire un SFINAE truc avec fuite de type de retour decltype(c.find(data), bool()) qui sera retiré de la surcharge de définir si le conteneur c en question n'a pas une fonction membre find(), et que les retours bool sinon (ce résultat est obtenu par l'abus de l' opérateur virgule à l'intérieur d' decltype)
    • un repli de surcharge qui prend simplement l' begin() et end() itérateurs et les délégués à l'origine in() la prise de deux itérateurs

Parce que les étiquettes pour l' detail_in() helper forme d'une hiérarchie d'héritage (un peu comme le standard de l'itérateur de balises), la première surcharge de match pour les conteneurs associatifs std::set et std::unordered_set et leur multi-cousins. Tous les autres conteneurs, y compris C-tableaux, std::array, std::vector et std::list, va correspondre à la deuxième surcharge.

#include <algorithm>
#include <array>
#include <initializer_list>
#include <type_traits>
#include <iostream>
#include <set>
#include <unordered_set>
#include <vector>

class foo
{
public:
    int data;

    template<class It>
    bool in(It first, It last) const
    {
        std::cout << "iterator overload: ";
        return std::find(first, last, data) != last;
    }

    template<class T>
    bool in(std::initializer_list<T> il) const
    {
        std::cout << "initializer_list overload: ";
        return in(begin(il), end(il));
    }

    template<class Container>
    bool in(Container const& c) const 
    {
        std::cout << "container overload: ";
        return detail_in(c, associative_container_tag{});    
    }

private:
    struct sequence_container_tag {};
    struct associative_container_tag: sequence_container_tag {};

    template<class AssociativeContainer>
    auto detail_in(AssociativeContainer const& c, associative_container_tag) const 
    -> decltype(c.find(data), bool())
    {
        std::cout << "associative overload: ";
        return c.find(data) != end(c);    
    }

    template<class SequenceContainer> 
    bool detail_in(SequenceContainer const& c, sequence_container_tag) const
    {
        std::cout << "sequence overload: ";
        using std::begin; using std::end;
        return in(begin(c), end(c));    
    }
};

int main()
{
    foo f{1};
    int a1[] = { 1, 2, 3};
    int a2[] = { 2, 3, 4};

    std::cout << f.in({1, 2, 3}) << "\n";
    std::cout << f.in({2, 3, 4}) << "\n";

    std::cout << f.in(std::begin(a1), std::end(a1)) << "\n";
    std::cout << f.in(std::begin(a2), std::end(a2)) << "\n";

    std::cout << f.in(a1) << "\n";
    std::cout << f.in(a2) << "\n";

    std::cout << f.in(std::array<int, 3>{ 1, 2, 3 }) << "\n";
    std::cout << f.in(std::array<int, 3>{ 2, 3, 4 }) << "\n";

    std::cout << f.in(std::vector<int>{ 1, 2, 3 }) << "\n";
    std::cout << f.in(std::vector<int>{ 2, 3, 4 }) << "\n";

    std::cout << f.in(std::set<int>{ 1, 2, 3 }) << "\n";
    std::cout << f.in(std::set<int>{ 2, 3, 4 }) << "\n";

    std::cout << f.in(std::unordered_set<int>{ 1, 2, 3 }) << "\n";
    std::cout << f.in(std::unordered_set<int>{ 2, 3, 4 }) << "\n";    
}

Exemple vivant que -pour tous les types de conteneurs- tirages de 1 et de 0 pour les deux séries.

Les cas d'utilisation pour l' std::initializer_list de surcharge sont pour les états-navire de test pour de petits ensembles de nombres que vous écrivez explicitement dans le code appelant. Il a O(N) de complexité, mais permet d'éviter toute allocations de tas.

Pour quelque chose de lourd, comme l'appartenance des tests de grands ensembles, vous pouvez stocker les numéros dans un conteneur associatif comme std::set, ou de ses multi_set ou unordered_set des cousins. Cela permettra d'aller au tas, lors du stockage de ces chiffres, mais a O(log N) ou même O(1) recherche de complexité.

Mais si vous avez juste un conteneur de séquence pleine de chiffres, vous pouvez également jeter que de la classe et il se fera un plaisir de calcul d'adhésion pour vous en O(N) du temps.

19voto

Lanting Points 414

Il existe de nombreuses façons de le faire avec le TSL.

Si vous avez un nombre incroyablement élevé d'éléments et que vous voulez tester si votre article est un membre de cet ensemble, utilisez set ou unordered_set. Ils vous permettent de vérifier l'adhésion log n et de la constante de temps respectivement.

Si vous gardez les éléments dans un tableau trié, puis binary_search permettra également de tester l'adhésion log n du temps.

Pour les petits tableaux, des linéaires search pourrait cependant préforme nettement plus rapide (car il n'existe pas de branchement). Une recherche linéaire pourrait même faire 3-8 des comparaisons dans le temps que prend le binaire de recherche pour "jump around". Ce blog suggère qu'il existe un point d'équilibre à ron 64 éléments, en dessous de laquelle un linéaire de recherche pourrait être plus rapide, mais cela dépend évidemment de la STL mise en œuvre, les optimisations du compilateur et de l'architecture de la direction de la prévision.

10voto

Angew Points 53063

Si data est vraiment un type entier ou énuméré, vous pouvez utiliser un switch :

 switch (data) {
  case 1:
  case 2:
  case 2000:
  case 6000:
  case /* whatever other values you want */:
    act_on_the_group();
    break;
  default:
    act_on_not_the_group();
    break;
}
 

9voto

Manu343726 Points 8803

La réponse à l'aide d' std::initializer_list sont beaux, mais je veux ajouter une autre solution possible qui est exactement ce que vous avez tenté avec que C variadic dans un type sécurisé et moderne: à l'Aide de C++11 variadic templates:

template<typename... NUMBERS>
bool any_equal( const foo& f , NUMBERS&&... numbers )
{
    auto unpacked = { numbers... };

    return std::find( std::begin( unpacked ) , std::end( unpacked ) , f.data ) 
           != std::end( unpacked );
};

Bien sûr, cela ne fonctionne que si toutes les valeurs transmises sont du même type. Si ce n'est la liste d'initialiseur unpacked ne peut pas être déduite ni initialisé.

Alors:

bool equals = any_equal( f , 1,2,3,4,5 );

EDIT: Voici un are_same metafunction pour s'assurer que tous les numéros passé sont du même type:

template<typename HEAD , typename... TAIL>
struct are_same : public and_op<std::is_same<HEAD,TAIL>::value...>
{};

and_op effectue n-aire et logique:

template<bool HEAD , bool... TAIL>
struct and_op : public std::integral_constant<bool,HEAD && and_op<TAIL...>::value>
{};

template<>
struct and_op<> : public std::true_type
{};

Cela rend possible de forcer l'utilisation des nombres de même type dans une manière simple:

template<typename... NUMBERS>
bool any_equal( const foo& f , NUMBERS&&... numbers )
{
    static_assert( all_same<NUMBERS...>::value , 
                   "ERROR: You should use numbers of the same type" );


    auto unpacked = { numbers... };

    return std::find( std::begin( unpacked ) , std::end( unpacked ) , f.data ) 
           != std::end( unpacked );
};

6voto

Potatoswatter Points 70305

Toute optimisation dépendra des propriétés de l’ensemble de nombres comparé.

S'il existe une limite supérieure définie, vous pouvez utiliser un std::bitset . Tester l'appartenance (c'est-à-dire, l'indexation dans le jeu de bits, qui se comporte comme un tableau), est O (1), en réalité quelques instructions rapides. C’est souvent la meilleure solution allant jusqu’à des centaines, bien que, selon l’application, des millions puissent être pratiques.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X