Herramientas de usuario

Herramientas del sitio


cpp-avanzado:macros

Macros para programación competitiva

Notar que en todos los casos, cuando queremos hablar de un rango de números, indicamos sus extremos como $[A, B)$, es decir, utilizamos como extremos dos valores A y B de modo tal que los números del rango son aquellos $x$ que cumplen $A \leq x < B$. Esta elección de “fronteras”, que es cerrada a la izquierda, pero abierta a la derecha, es para casi toda tarea de programación la más conveniente, y se utiliza mucho en los lenguajes de programación como C++. También es la que más recomendamos, porque evita muchos errores y es más clara que otras, una vez que uno la conoce.

Para iterar los números desde 0 hasta n-1 inclusive, con la variable i que es creada para el for:

#define forn(i,n) for(int i=0;i<int(n);i++)

Para iterar los números desde s hasta n-1 inclusive, con la variable i que es creada para el for:

#define forsn(i,s,n) for(int i=int(s);i<int(n);i++)

Para iterar los números desde n-1 hasta 0 inclusive, bajando con la variable i que es creada para el for:

#define dforn(i,n) for(int i=int(n)-1;i>=0;i--)

Para iterar los números desde n-1 hasta s inclusive, bajando con la variable i que es creada para el for:

#define dforsn(i,s,n) for(int i=int(n)-1;i>=int(s);i--)

Las siguientes macros son cómodas para iterar una colección hacia adelante o hacia atrás, si nos interesa tener acceso al iterador y no solamente a los elementos iterados. Eran necesarias (utilizando typeof en lugar de auto) antes de c++11 para iterar sets y maps, pues no existía el foreach.

#define forall(i,c) for(auto i = (c).begin(); i != (c).end(); i++)
#define dforall(i,c) for(auto i = (c).rbegin(); i != (c).rend(); i++)

La siguiente es muy útil para utilizar con funciones de STL, donde se suele pedir un rango mediante dos iteradores, para pasar directamente una colección completa.

#define all(c) (c).begin(),(c).end()

El uso más común de esta macro, es para llamar a la función sort, con sort(all(v)) por ejemplo, siendo v un vector. Sin embargo es útil con muchísimas funciones de la STL, como podría ser por ejemplo una instrucción find(all(v), 27).

La siguiente sirve para consultar si un elemento está en un set (o map: en un map, se consulta si el elemento dado es una clave del diccionario).

#define esta(x,c) ((c).find(x) != (c).end())

Las siguientes son muy útiles para buscar y corregir errores en programas:

#define DBG(x) cerr << #x << " = " << (x) << endl
#define RAYA cerr << "===============================" << endl

DBG es una macro particularmente bonita y “mágica”. Si escribimos en una línea DBG(x); en el código, siendo x una variable (o podría ser una expresión), nos mostrará su valor y su nombre por pantalla. Alentamos a probarla para ver su utilidad a la hora de buscar bugs en programas.

Similarmente, RAYA; muestra una clara línea separadora: esto es especialmente útil cuando tenemos un for y queremos mostrar los valores de varias variables en cada iteración. Poniendo un RAYA;, podemos separar con facilidad los valores entre iteraciones.

Los siguientes typedef son razonables y cómodos en programación competitiva:

typedef long long tint;
typedef long double tdbl;
typedef vector<int> vint;
typedef pair<int,int> pint;
typedef pair<tint,tint> ptint;

El porqué de la macro forn

#define forn(i, n) for(int i = 0; i < int(n); i++)

nos permite, además de parametrizar la cantidad de iteraciones, parametrizar la variable que indexa. Entonces uno puede escribir

forn(i, 10){
    forn (j, v.size()) {
 
    }
}

(Comentario: en la declaración de la macro hay un int(n) que nos permite que el forn hasta v.size()-1 y similares expresiones no tenga un posible bug causado por ser este valor de tipo unsigned, y de paso logra que el compilador no emita un warning por esto.)

Uno podría entusiarmarse con las macros y hacer la siguiente macro:

#define forn(n) for(int i = 0; i < int(n); i++)

Es una muy mala idea definir el forn sin pasarle la variable para indexar, porque eso nos podria introducir “bugs ocultos” por no ser suficientemente declarativos y esconder cosas en la macro. En este caso, escondemos una declaración de variables.

Por ejemplo, este código tendría un error oculto usando esa macro

int i = 24;
forn(10){
	cout << i << endl;
}

Un lector cualquiera asumiría que este código escribe 10 veces el número 24, pero se encontraría con que imprime los números del 1 al 10.

En general el objetivo de las macros no es escribir menos caracteres, sino escribir código que evite bugs simples por repetición o copy-paste.

for(int i=0;i<n;i++){
    for(int j=0;j<m;i++){
 
   }
}

Ese código tiene un bug no evidente, en el segundo for incrementa i en vez de j. En cambio, usando la macro forn como la definimos más arriba, sería

forn(i, n) {
   forn(j, m) {
 
   }
}

¡Donde ese bug es simplemente imposible de escribir!

cpp-avanzado/macros.txt · Última modificación: 2017/12/05 23:26 por santo