c++ - simple - que son oraciones truncas
Forma compacta de escribir una declaración if(..) con muchas igualdades (9)
Primero, recomiendo usar un bucle
for
, que es la solución más fácil y fácil de leer:
for (i = 0; i < n; i++) { if (var == eq[i]) { // if true break; } }
Sin embargo, algunos otros métodos también están disponibles, por ejemplo,
std::all_of
,
std::any_of
,
std::none_of
(en
#include <algorithm>
).
Veamos el sencillo programa de ejemplo que contiene todas las palabras clave anteriores.
#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";
}
}
¿Hay una mejor manera de escribir código como este:
if (var == "first case" or var == "second case" or var == "third case" or ...)
En Python puedo escribir:
if var in ("first case", "second case", "third case", ...)
lo que también me da la oportunidad de pasar fácilmente la lista de buenas opciones:
good_values = "first case", "second case", "third case"
if var in good_values
Esto es solo un ejemplo: el tipo de
var
puede ser diferente de una cadena, pero solo estoy interesado en comparaciones alternativas (
or
) (
==
).
var
puede ser no
const
, mientras que la lista de opciones se conoce en tiempo de compilación.
Bonificación profesional:
-
pereza
or
- tiempo de compilación desenrollado de bucle
-
fácil de extender a otros operadores que
==
El algoritmo
any_of
podría funcionar razonablemente bien aquí:
#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; });
(Es posible que desee limitar el alcance de los
tokens
al contexto mínimo requerido).
O crea una plantilla de contenedor:
#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));
}
Uso:
bool b = any_of_c({"abc", "def", "ghi"},
[&var](const char * s) { return s == var; });
Lo más parecido sería algo como:
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();
}
Necesitamos tomar un argumento de tipo
initializer_list<U>
para que podamos pasar una
lista de inicialización arriostrada
como
{a,b,c}
.
Esto copia los elementos, pero presumiblemente lo haremos porque estamos proporcionando literales, por lo que probablemente no sea un gran problema.
Podemos usar eso así:
std::string var = "hi";
bool b = in(var, {"abc", "def", "ghi", "hi"});
std::cout << b << std::endl; // true
Muy bien, entonces, quieres la modificación del lenguaje radical . Específicamente, desea crear su propio operador. Listo?
Sintaxis
Voy a modificar la sintaxis para usar una lista de estilo C y C ++:
if (x in {x0, ...}) ...
Además, dejaremos que nuestro nuevo operador
in se
aplique a cualquier contenedor para el que se definan
begin()
y
end()
:
if (x in my_vector) ...
Hay una advertencia: no es un operador verdadero y, por lo tanto, siempre debe estar entre paréntesis como su propia expresión:
bool ok = (x in my_array);
my_function( (x in some_sequence) );
El código
Lo primero que debe tener en cuenta es que RLM a menudo requiere un poco de abuso de macro y operador. Afortunadamente, para un predicado de membresía simple, el abuso en realidad no es tan malo.
#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
Uso
¡Excelente! Ahora podemos usarlo de todas las formas en que esperaría que un operador in sea útil. Según su interés particular, vea el ejemplo 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";
}
Posibles mejoras
Siempre hay cosas que se pueden hacer para mejorar el código para sus propósitos específicos.
Puede agregar un operador
ni
(no incluido) (Agregar un nuevo tipo de contenedor thunk).
Puede envolver los contenedores de enlaces en un espacio de nombres (una buena idea).
Puede especializarse en cosas como
std::set
para usar la función miembro
.count()
lugar de la búsqueda O (n).
Etc.
Sus otras preocupaciones
-
const
vsmutable
: no es un problema; ambos son utilizables con el operador -
pereza
or
: Técnicamente,or
no es perezoso, está en cortocircuito. El algoritmostd::find()
también cortocircuita de la misma manera. -
tiempo de compilación desenrollado de bucle: no es realmente aplicable aquí.
Su código original no usó bucles;
mientras que
std::find()
hace, cualquier desenrollamiento de bucle que pueda ocurrir depende del compilador. -
fácil de extender a operadores que no sean
==
: Eso en realidad es un problema separado; ya no está mirando un predicado de membresía simple, sino que ahora está considerando un filtro plegable funcional. Es completamente posible crear un algoritmo que haga eso, pero la Biblioteca Estándar proporciona la funciónany_of()
, que hace exactamente eso. (Simplemente no es tan bonito como nuestro operador RLM ''in''. Dicho esto, cualquier programador de C ++ lo entenderá fácilmente. Estas respuestas ya se han ofrecido aquí).
Espero que esto ayude.
Podrías usar una caja de interruptor. En lugar de tener una lista de casos separados, podría tener:
incluir
usando el espacio de nombres estándar;
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;
}
Para que pueda agrupar sus resultados: A, B y C generarán "bien hecho". Tomé este ejemplo de Tutorials Point: http://www.tutorialspoint.com/cplusplus/cpp_switch_statement.htm
Puede usar std :: set para probar si var le pertenece. (Compilar con c ++ 11 habilitado)
#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;
}
La ventaja es que
std::set<std::string>::count
funciona en tiempo
O(log(n))
(donde
n
es el número de cadenas para probar) en comparación con no compacto
if
bruja es
O(n)
en general.
La desventaja es que la construcción del conjunto toma
O(n*log(n))
.
Entonces, construya una vez, como:
static std::set<std::string> the_set = {"abc", "def", "ghi"};
Pero, en mi opinión, sería mejor dejar la condición como está, a menos que contenga más de 10 cadenas para verificar.
Las ventajas de rendimiento del uso de std :: set para tal prueba aparecen solo para
n
grande.
Además, simple no compacto
if
es más fácil de leer para un desarrollador promedio de c ++.
Si tiene acceso a C ++ 14 (no estoy seguro si esto funciona con C ++ 11) podría escribir algo como esto:
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; });
};
Una llamada se vería así:
std::string test_case = ...;
if (is_one_of<std::string>(test_case, { "first case", "second case", "third case" })) {...}
o así
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 no desea "ajustar" los casos permitidos en un tipo de lista, también puede escribir una pequeña función auxiliar como esta:
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...});
};
Esto le permitirá llamarlo así:
std::string test_case = ...;
if (is_one_of<std::string>(test_case, "first case", "second case", "third case" )) {...}
Vale la pena señalar que en la mayoría de los códigos Java y C ++ que he visto, enumerar 3 o más condicionales es la práctica aceptada. Ciertamente es más legible que las soluciones "inteligentes". Si esto sucede con tanta frecuencia, es un gran obstáculo, de todos modos, es un olor a diseño y un enfoque con plantilla o polimórfico probablemente ayudaría a evitar esto.
Entonces mi respuesta es la operación "nula". Solo sigue haciendo lo más detallado, es lo más aceptado.
si quieres expandirlo en tiempo de compilación puedes usar algo como esto
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");
En realidad no lo probé, y la idea proviene de la charla de Alexandrescu ''Las plantillas variables son funódicas''. Entonces, para los detalles (y la implementación adecuada) mira eso.
Editar: en c ++ 17 introdujeron una buena sintaxis de expresión de pliegue
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