web-dev-qa-db-fra.com

Manière compacte d'écrire une instruction if (..) avec de nombreuses égalités

Existe-t-il une meilleure façon d'écrire du code comme celui-ci:

if (var == "first case" or var == "second case" or var == "third case" or ...)

En Python je peux écrire:

if var in ("first case", "second case", "third case", ...)

ce qui me donne également la possibilité de passer facilement la liste des bonnes options:

good_values = "first case", "second case", "third case"
if var in good_values

Ceci est juste un exemple: le type de var peut être différent d'une chaîne, mais je ne suis intéressé que par des comparaisons alternatives (or) (==). var peut être non -const, tandis que la liste des options est connue au moment de la compilation.

Bonus Pro:

  • paresse de or
  • compiler le déroulement de la boucle de temps
  • facile à étendre à d'autres opérateurs que ==
55
Ruggero Turra

si vous voulez étendre le temps de compilation, vous pouvez utiliser quelque chose comme ça

template<class T1, class T2>
bool isin(T1&& t1, T2&& t2) {
   return t1 == t2;
}

template<class T1, class T2, class... Ts>
bool isin(T1&& t1 , T2&& t2, T2&&... ts) {
   return t1 == t2 || isin(t1, ts...);
}

std::string my_var = ...; // somewhere in the code
...
bool b = isin(my_var, "fun", "gun", "hun");

Je ne l'ai pas testé en fait, et l'idée vient du discours d'Alexandrescu "Les modèles Variadic sont funadiques". Donc, pour les détails (et la bonne mise en œuvre), regardez cela.

Edit: en c ++ 17 ils ont introduit une syntaxe Nice expression de repli

template<typename... Args>
bool all(Args... args) { return (... && args); }

bool b = all(true, true, true, false);
 // within all(), the unary left fold expands as
 //  return ((true && true) && true) && false;
 // b is false
44
Slava

Le any_of l'algorithme pourrait fonctionner assez bien ici:

#include <algorithm>
#include <initializer_list>

auto tokens = { "abc", "def", "ghi" };

bool b = std::any_of(tokens.begin(), tokens.end(),
                     [&var](const char * s) { return s == var; });

(Vous pouvez limiter la portée de tokens au contexte minimal requis.)

Ou vous créez un modèle de wrapper:

#include <algorithm>
#include <initializer_list>
#include <utility>

template <typename T, typename F>
bool any_of_c(const std::initializer_list<T> & il, F && f)
{
    return std::any_of(il.begin(), il.end(), std::forward<F>(f));
}

Usage:

bool b = any_of_c({"abc", "def", "ghi"},
                  [&var](const char * s) { return s == var; });
27
Kerrek SB

Très bien alors, vous voulez Modification du langage radical. Plus précisément, vous souhaitez créer votre propre opérateur. Prêt?

Syntaxe

Je vais modifier la syntaxe pour utiliser une liste de style C et C++:

if (x in {x0, ...}) ...

De plus, nous laisserons notre nouvel opérateur in s'appliquer à tout conteneur pour lequel begin() et end() sont définis:

if (x in my_vector) ...

Il y a une mise en garde: ce n'est pas un véritable opérateur et il doit donc toujours être entre parenthèses comme sa propre expression:

bool ok = (x in my_array);

my_function( (x in some_sequence) );

Le code

La première chose à savoir est que RLM nécessite souvent des abus de macro et d'opérateur. Heureusement, pour un simple prédicat d'adhésion, l'abus n'est en fait pas si mal.

#ifndef DUTHOMHAS_IN_OPERATOR_HPP
#define DUTHOMHAS_IN_OPERATOR_HPP

#include <algorithm>
#include <initializer_list>
#include <iterator>
#include <type_traits>
#include <vector>

//----------------------------------------------------------------------------
// The 'in' operator is magically defined to operate on any container you give it
#define in , in_container() =

//----------------------------------------------------------------------------
// The reverse-argument membership predicate is defined as the lowest-precedence 
// operator available. And conveniently, it will not likely collide with anything.
template <typename T, typename Container>
typename std::enable_if <!std::is_same <Container, T> ::value, bool> ::type
operator , ( const T& x, const Container& xs )
{
  using std::begin;
  using std::end;
  return std::find( begin(xs), end(xs), x ) != end(xs);
}

template <typename T, typename Container>
typename std::enable_if <std::is_same <Container, T> ::value, bool> ::type
operator , ( const T& x, const Container& y )
{
  return x == y;
}

//----------------------------------------------------------------------------
// This thunk is used to accept any type of container without need for 
// special syntax when used.
struct in_container
{
  template <typename Container>
  const Container& operator = ( const Container& container )
  {
    return container;
  }

  template <typename T>
  std::vector <T> operator = ( std::initializer_list <T> xs )
  {
    return std::vector <T> ( xs );
  }
};

#endif

tilisation

Génial! Maintenant, nous pouvons l'utiliser dans tous les façons dont vous vous attendriez à ce qu'un opérateur in soit utile. Selon votre intérêt particulier, voir l'exemple 3:

#include <iostream>
#include <set>
#include <string>
using namespace std;

void f( const string& s, const vector <string> & ss ) { cout << "nope\n\n"; }
void f( bool b ) { cout << "fooey!\n\n"; }

int main()
{
  cout << 
    "I understand three primes by digit or by name.\n"
    "Type \"q\" to \"quit\".\n\n";

  while (true)
  {
    string s;
    cout << "s? ";
    getline( cin, s );

    // Example 1: arrays 
    const char* quits[] = { "quit", "q" };
    if (s in quits) 
      break;

    // Example 2: vectors
    vector <string> digits { "2", "3", "5" };
    if (s in digits)
    {
      cout << "a prime digit\n\n";
      continue;
    }

    // Example 3: literals
    if (s in {"two", "three", "five"})
    {
      cout << "a prime name!\n\n";
      continue;
    }

    // Example 4: sets
    set <const char*> favorites{ "7", "seven" };
    if (s in favorites)
    {
      cout << "a favorite prime!\n\n";
      continue;
    }

    // Example 5: sets, part deux
    if (s in set <string> { "TWO", "THREE", "FIVE", "SEVEN" })
    {
      cout << "(ouch! don't shout!)\n\n";
      continue;
    }

    // Example 6: operator weirdness
    if (s[0] in string("014") + "689")
    {
      cout << "not prime\n\n";
      continue;
    }

    // Example 7: argument lists unaffected    
    f( s, digits );
  }
  cout << "bye\n";
}

Améliorations potentielles

Il y a toujours des choses à faire pour améliorer le code pour vos besoins spécifiques. Vous pouvez ajouter un opérateur ni (non-in) (Ajouter un nouveau type de conteneur thunk). Vous pouvez envelopper les conteneurs thunk dans un espace de noms (une bonne idée). Vous pouvez vous spécialiser sur des choses comme std::set Pour utiliser la fonction membre .count() au lieu de la recherche O(n). Etc.

Vos autres préoccupations

  • const vs mutable: pas de problème; les deux sont utilisables avec l'opérateur
  • paresse de or: Techniquement, or n'est pas pas paresseux, il est court-circuité. L'algorithme std::find() court-circuite également de la même manière.
  • compiler le déroulement de la boucle temporelle: pas vraiment applicable ici. Votre code d'origine n'utilisait pas de boucles; tandis que std::find() le fait, tout déroulement de boucle qui peut se produire appartient au compilateur.
  • facile à étendre aux opérateurs autres que ==: C'est en fait un problème distinct; vous ne regardez plus un simple prédicat d'appartenance, mais envisagez maintenant un filtre de pliage fonctionnel. Il est tout à fait possible de créer un algorithme qui fait cela, mais la bibliothèque standard fournit la fonction any_of(), qui fait exactement cela. (Ce n'est tout simplement pas aussi joli que notre opérateur RLM 'in'. Cela dit, tout programmeur C++ le comprendra facilement. De telles réponses ont déjà été proposées ici.)

J'espère que cela t'aides.

11
Dúthomhas

Tout d'abord, je recommande d'utiliser une boucle for, qui est à la fois la solution la plus simple et la plus lisible:

for (i = 0; i < n; i++) {
   if (var == eq[i]) {
      // if true
      break;
   }
}

Cependant, d'autres méthodes sont également disponibles, par exemple std::all_of, std::any_of, std::none_of (dans #include <algorithm>).

Regardons le programme d'exemple simple qui contient tous les mots-clés ci-dessus

#include <vector>
#include <numeric>
#include <algorithm>
#include <iterator>
#include <iostream>
#include <functional>

int main()
{
    std::vector<int> v(10, 2);
    std::partial_sum(v.cbegin(), v.cend(), v.begin());
    std::cout << "Among the numbers: ";
    std::copy(v.cbegin(), v.cend(), std::ostream_iterator<int>(std::cout, " "));
    std::cout << '\\n';

    if (std::all_of(v.cbegin(), v.cend(), [](int i){ return i % 2 == 0; })) 
    {
        std::cout << "All numbers are even\\n";
    }
    if (std::none_of(v.cbegin(), v.cend(), std::bind(std::modulus<int>(),
                                  std::placeholders::_1, 2))) 
    {
        std::cout << "None of them are odd\\n";
    }
    struct DivisibleBy
    {
        const int d;
        DivisibleBy(int n) : d(n) {}
        bool operator()(int n) const { return n % d == 0; }
    };

    if (std::any_of(v.cbegin(), v.cend(), DivisibleBy(7))) 
    {
        std::cout << "At least one number is divisible by 7\\n";
    }
}
9
Embedded C

Vous pouvez utiliser std :: set pour tester si var lui appartient. (Compilation avec c ++ 11 activé)

#include <iostream>
#include <set>

int main()
{
    std::string el = "abc";

    if (std::set<std::string>({"abc", "def", "ghi"}).count(el))
        std::cout << "abc belongs to {\"abc\", \"def\", \"ghi\"}" << std::endl;

    return 0;
}

L'avantage est que std::set<std::string>::count Fonctionne en O(log(n)) temps (où est n est le nombre de chaînes à tester) comparé à non compact if qui est O(n) en général. L'inconvénient est que la construction de l'ensemble prend O(n*log(n)). Donc, construisez-le une fois, comme:

static std::set<std::string> the_set = {"abc", "def", "ghi"};

Mais, OMI, il serait préférable de laisser la condition telle qu'elle est, à moins qu'elle ne contienne plus de 10 chaînes à vérifier. Les avantages de performances de l'utilisation de std :: set pour un tel test n'apparaissent que pour les gros n. De plus, un simple if simple non compact est plus facile à lire pour un développeur c ++ moyen.

6
ivaigult

La chose la plus proche serait quelque chose comme:

template <class K, class U, class = decltype(std::declval<K>() == std::declval<U>())>
bool in(K&& key, std::initializer_list<U> vals)
{
    return std::find(vals.begin(), vals.end(), key) != vals.end();
}

Nous devons prendre un argument de type initializer_list<U> pour que nous puissions passer une braced-init-list comme {a,b,c}. Cela copie les éléments, mais nous allons probablement le faire parce que nous fournissons des littéraux donc ce n'est probablement pas un gros problème.

Nous pouvons l'utiliser comme ceci:

std::string var = "hi";    
bool b = in(var, {"abc", "def", "ghi", "hi"});
std::cout << b << std::endl; // true
5
Barry

Si vous avez accès à C++ 14 (vous ne savez pas si cela fonctionne avec C++ 11), vous pouvez écrire quelque chose comme ceci:

template <typename T, typename L = std::initializer_list<T>>
constexpr bool is_one_of(const T& value, const L& list)
{
    return std::any_of(std::begin(list), std::end(list), [&value](const T& element) { return element == value; });
};

Un appel ressemblerait à ceci:

std::string test_case = ...;
if (is_one_of<std::string>(test_case, { "first case", "second case", "third case" })) {...}

ou comme ça

std::string test_case = ...;
std::vector<std::string> allowedCases{ "first case", "second case", "third case" };
if (is_one_of<std::string>(test_case, allowedCases)) {...}

Si vous n'aimez pas "encapsuler" les cas autorisés dans un type de liste, vous pouvez également écrire une petite fonction d'aide comme celle-ci:

template <typename T, typename...L>
constexpr bool is_one_of(const T& value, const T& first, const L&... next) //First is used to be distinct
{
    return is_one_of(value, std::initializer_list<T>{first, next...});
};

Cela vous permettra de l'appeler comme ceci:

std::string test_case = ...;
if (is_one_of<std::string>(test_case, "first case", "second case", "third case" )) {...}

Exemple complet sur Colir

4
Simon Kraemer

Il convient de noter que dans la plupart des Java et code C++ que j'ai vus, répertorier environ 3 conditions) est la pratique acceptée. C'est certainement plus lisible que les solutions "intelligentes". Si cela se produit si souvent, c'est un frein majeur, c'est une odeur de conception de toute façon et une approche basée sur des modèles ou polymorphe aiderait probablement à éviter cela.

Donc ma réponse est l'opération "nulle". Continuez à faire la chose la plus verbeuse, c'est le plus accepté.

2
djechlin

Vous pouvez utiliser un boîtier de commutation. Au lieu d'avoir une liste de cas distincts, vous pourriez avoir:

comprendre

using namespace std;

int main () {char grade = 'B';

switch(grade)
{
case 'A' :
case 'B' :
case 'C' :
    cout << "Well done" << endl;
    break;
case 'D' :
    cout << "You passed" << endl;
    break;
case 'F' :
    cout << "Better try again" << endl;
    break;

default :
    cout << "Invalid grade" << endl;

}

cout << "Your grade is " << grade << endl;

return 0;

}

Ainsi, vous pouvez regrouper vos résultats: A, B et C sortiront "bien fait". J'ai pris cet exemple de Tutorials Point: http://www.tutorialspoint.com/cplusplus/cpp_switch_statement.htm

0
Mike Cave