Antecedentes
Para mis envíos de código de golf en C, necesito una herramienta de procesamiento. Al igual que en muchos otros lenguajes, el espacio en blanco es irrelevante en su mayoría en la fuente C (¡pero no siempre!) - aún hace que el código sea mucho más comprensible para los humanos. Un programa C completamente desarrollado que no contiene un solo espacio en blanco redundante a menudo es apenas legible.
Por lo tanto, me gusta escribir mi código en C para un envío de código de golf que incluye espacios en blanco y, a veces, comentarios, para que el programa mantenga una estructura comprensible mientras escribo. El último paso es eliminar todos los comentarios y espacios en blanco redundantes. Esta es una tarea tediosa y sin sentido que realmente debe hacer un interno en un programa de computadora.
Tarea
Escriba un programa o función que elimine comentarios y espacios en blanco redundantes de alguna fuente de C "pre-golf" de acuerdo con las siguientes reglas:
- A
\
(barra invertida) como el último carácter de una línea es una continuación de línea . Si encuentra esto, debe tratar la siguiente línea como parte de la misma línea lógica (podría, por ejemplo, eliminar completamente la\
y la siguiente\n
(nueva línea) antes de hacer cualquier otra cosa) - Los comentarios solo usarán el formato de una línea, comenzando con
//
. Por lo tanto, para eliminarlos, ignora el resto de la línea lógica siempre que se encuentre//
fuera de un literal de cadena (consulte a continuación). - Los caracteres de
espacio en blanco son (espacio),
\t
(tabulación) y\n
(nueva línea, así que aquí el final de una línea lógica). Cuando encuentre una secuencia de espacios en blanco, examine los caracteres que no lo son. Si
- ambos son alfanuméricos o subrayados (rango
[a-zA-Z0-9_]
) o - ambos son
+
o - ambos son
-
o - el anterior es
/
y el siguiente es*
luego reemplace la secuencia con un solo espacio (
).
De lo contrario, elimine la secuencia por completo.
Esta regla tiene algunas excepciones :
- Las directivas de preprocesador deben aparecer en sus propias líneas en su salida. Una directiva de preprocesador es una línea que comienza con
#
. - Dentro de un literal de cadena o literal de caracteres , no debe eliminar ningún espacio en blanco. Cualquier
"
(comilla doble) /'
(comilla simple) que no esté precedida directamente por un número impar de barras invertidas (\
) comienza o finaliza una cadena literal / carácter literal . Le garantizamos que los literales de cadena y caracteres terminan en la misma línea que comenzaron. literales de cadena y literales de caracteres no se pueden anidar, por lo que un'
interior de un literal de cadena , así como"
el interior de un carácter literal no tienen ningún significado especial.
- ambos son alfanuméricos o subrayados (rango
Especificación de E / S
La entrada y la salida deben ser secuencias de caracteres (cadenas) que incluyen caracteres de nueva línea o matrices / listas de cadenas que no contienen caracteres de nueva línea. Si elige usar matrices / listas, cada elemento representa una línea, por lo que las nuevas líneas están implícitas después de cada elemento.
Puede suponer que la entrada es un código fuente de programa C válido. Esto también significa que solo contiene caracteres ASCII imprimibles, pestañas y líneas nuevas. Se permite un comportamiento indefinido en la entrada con formato incorrecto.
Espacio inicial y final / líneas vacías son no permitidos .
Casos de prueba
entrada
main() { printf("Hello, World!"); // hi }
salida
main(){printf("Hello, World!");}
entrada
#define max(x, y) \ x > y ? x : y #define I(x) scanf("%d", &x) a; b; // just a needless comment, \ because we can! main() { I(a); I(b); printf("\" max \": %d\n", max(a, b)); }
salida
#define max(x,y)x>y?x:y #define I(x)scanf("%d",&x) a;b;main(){I(a);I(b);printf("\" max \": %d\n",max(a,b));}
entrada
x[10];*c;i; main() { int _e; for(; scanf("%d", &x) > 0 && ++_e;); for(c = x + _e; c --> x; i = 100 / *x, printf("%d ", i - --_e)); }
salida
x[10];*c;i;main(){int _e;for(;scanf("%d",&x)>0&&++_e;);for(c=x+_e;c-->x;i=100/ *x,printf("%d ",i- --_e));}
entrada
x; #include <stdio.h> int main() { puts("hello // there"); }
salida
x; #include<stdio.h> int main(){puts("hello // there");}
input (un ejemplo del mundo real)
// often used functions/keywords: #define P printf( #define A case #define B break // loops for copying rows upwards/downwards are similar -> macro #define L(i, e, t, f, s) \ for (o=i; o e;){ strcpy(l[o t], l[o f]); c[o t]=c[s o]; } // range check for rows/columns is similar -> macro #define R(m,o) { return b<1|b>m ? m o : b; } // checking for numerical input is needed twice (move and print command): #define N(f) sscanf(f, "%d,%d", &i, &j) || sscanf(f, ",%d", &j) // room for 999 rows with each 999 cols (not specified, should be enough) // also declare "current line pointers" (*L for data, *C for line length), // an input buffer (a) and scratch variables r, i, j, o, z, c[999], *C, x=1, y=1; char a[999], l[999][999], (*L)[999]; // move rows down from current cursor position D() { L(r, >y, , -1, --) r++ ? strcpy(l[o], l[o-1]+--x), c[o-1]=x, l[o-1][x]=0 : 0; c[y++] = strlen(l[o]); x=1; } // move rows up, appending uppermost to current line U() { strcat(*L, l[y]); *C = strlen(*L); L(y+1, <r, -1, , ++) --r; *l[r] = c[r] = 0; } // normalize positions, treat 0 as max X(b) R(c[y-1], +1) Y(b) R(r, ) main() { for(;;) // forever { // initialize z as current line index, the current line pointers, // i and j for default values of positioning z = i = y; L = l + --z; C = c + z; j = x; // prompt: !r || y/r && x > *C ? P "end> ") : P "%d,%d> ", y, x); // read a line of input (using scanf so we don't need an include) scanf("%[^\n]%*c", a) // no command arguments -> make check easier: ? a[2] *= !!a[1], // numerical input -> have move command: // calculate new coordinates, checking for "relative" N(a) ? y = Y(i + (i<0 | *a=='+') * y) , x = X(j + (j<0 || strchr(a+1, '+')) * x) :0 // check for empty input, read single newline // and perform <return> command: : ( *a = D(), scanf("%*c") ); switch(*a) { A 'e': y = r; x = c[r-1] + 1; B; A 'b': y = 1; x = 1; B; A 'L': for(o = y-4; ++o < y+2;) o<0 ^ o<r && P "%c%s\n", o^z ? ' ' : '>', l[o]); for(o = x+1; --o;) P " "); P "^\n"); B; A 'l': puts(*L); B; A 'p': i = 1; j = 0; N(a+2); for(o = Y(i)-1; o<Y(j); ++o) puts(l[o]); B; A 'A': y = r++; strcpy(l[y], a+2); x = c[y] = strlen(a+2); ++x; ++y; B; A 'i': D(); --y; x=X(0); // Commands i and r are very similar -> fall through // from i to r after moving rows down and setting // position at end of line: A 'r': strcpy(*L+x-1, a+2); *C = strlen(*L); x = 1; ++y > r && ++r; B; A 'I': o = strlen(a+2); memmove(*L+x+o-1, *L+x-1, *C-x+1); *C += o; memcpy(*L+x-1, a+2, o); x += o; B; A 'd': **L ? **L = *C = 0, x = 1 : U(); y = y>r ? r : y; B; A 'j': y<r && U(); } } }
salida
#define P printf( #define A case #define B break #define L(i,e,t,f,s)for(o=i;o e;){strcpy(l[o t],l[o f]);c[o t]=c[s o];} #define R(m,o){return b<1|b>m?m o:b;} #define N(f)sscanf(f,"%d,%d",&i,&j)||sscanf(f,",%d",&j) r,i,j,o,z,c[999],*C,x=1,y=1;char a[999],l[999][999],(*L)[999];D(){L(r,>y,,-1,--)r++?strcpy(l[o],l[o-1]+--x),c[o-1]=x,l[o-1][x]=0:0;c[y++]=strlen(l[o]);x=1;}U(){strcat(*L,l[y]);*C=strlen(*L);L(y+1,<r,-1,,++)--r;*l[r]=c[r]=0;}X(b)R(c[y-1],+1)Y(b)R(r,)main(){for(;;){z=i=y;L=l+--z;C=c+z;j=x;!r||y/r&&x>*C?P"end> "):P"%d,%d> ",y,x);scanf("%[^\n]%*c",a)?a[2]*=!!a[1],N(a)?y=Y(i+(i<0|*a=='+')*y),x=X(j+(j<0||strchr(a+1,'+'))*x):0:(*a=D(),scanf("%*c"));switch(*a){A'e':y=r;x=c[r-1]+1;B;A'b':y=1;x=1;B;A'L':for(o=y-4;++o<y+2;)o<0^o<r&&P"%c%s\n",o^z?' ':'>',l[o]);for(o=x+1;--o;)P" ");P"^\n");B;A'l':puts(*L);B;A'p':i=1;j=0;N(a+2);for(o=Y(i)-1;o<Y(j);++o)puts(l[o]);B;A'A':y=r++;strcpy(l[y],a+2);x=c[y]=strlen(a+2);++x;++y;B;A'i':D();--y;x=X(0);A'r':strcpy(*L+x-1,a+2);*C=strlen(*L);x=1;++y>r&&++r;B;A'I':o=strlen(a+2);memmove(*L+x+o-1,*L+x-1,*C-x+1);*C+=o;memcpy(*L+x-1,a+2,o);x+=o;B;A'd':**L?**L=*C=0,x=1:U();y=y>r?r:y;B;A'j':y<r&&U();}}}
Este es el código de golf , por lo que gana la respuesta válida más corta (en bytes).
Respuestas:
Pip ,
148135133138 bytesBytes se cuentan en CP-1252 , de modo
¶
y·
son de un byte cada uno. Tenga en cuenta que esto espera que el código C sea un argumento de línea de comando único, que (en una línea de comando real) requeriría el uso de copiosas secuencias de escape. ¡Es mucho más fácil probarlo en línea!Explicación de la versión ungolfed
El código realiza un montón de operaciones de reemplazo, con un par de trucos.
Continuaciones de barra invertida
Todos
RM
los casos de la cadena literales decir, barra invertida seguida de nueva línea.
Literales de cadena y caracteres
Utilizamos un reemplazo de expresiones regulares con una función de devolución de llamada:
La expresión regular coincide con una comilla simple o doble, seguida de una no codiciosa
.*?
que coincide con 0 o más caracteres, la menor cantidad posible. Tenemos una mirada negativa hacia atrás para asegurar que el personaje anterior no sea una barra invertida; luego hacemos coincidir un número par de barras invertidas seguidas por el delimitador de apertura nuevamente.La función de devolución de llamada toma el literal de cadena / carácter y lo empuja al final de la lista
l
. Luego devuelve un carácter que comienza con el código de carácter 192 (À
) y aumenta con cada literal reemplazado. Por lo tanto, el código se transforma así:Se garantiza que estos caracteres de reemplazo no aparecerán en el código fuente, lo que significa que podemos reemplazarlos sin ambigüedad más adelante.
Comentarios
La expresión regular coincide
//
más todo hasta la nueva línea y se reemplaza conx
(preestablecido en la cadena vacía).Directivas del pre procesador
Envuelve ejecuciones de caracteres que no son de nueva línea que comienzan con un inicio de sesión
¶
.Espacios que no deben ser eliminados
Están pasando muchas cosas aquí. La primera parte genera esta lista de expresiones regulares para reemplazar:
Tenga en cuenta el uso de lookaheads para hacer coincidir, por ejemplo, solo el
e
indefine P printf
. De esta manera, esta coincidencia no consume elP
, lo que significa que la próxima coincidencia puede usarlo.Generamos esta lista de expresiones regulares asignando una función a una lista, donde la lista contiene
y la función hace esto a cada elemento:
Una vez que tenemos nuestras expresiones regulares, reemplazamos sus ocurrencias con esta función de devolución de llamada:
que reemplaza la ejecución de espacios en blanco en cada partido con
·
.Eliminación y limpieza de espacios en blanco.
Tres reemplazos sucesivos sustituyen las ejecuciones restantes de espacio en blanco (
w
) por una cadena vacía (x
), ejecuciones de¶
nueva línea y·
espacio.Sustitución inversa de cadenas y literales de caracteres
Construimos una lista de todos los caracteres que usamos como sustituciones de literales tomando
192 + range(len(l))
y convirtiendo en caracteres. Entonces podemos reemplazar cada uno de estos con su literal asociado enl
.¡Y eso es! La cadena resultante se imprime automáticamente.
fuente
//
literal dentro de una cadena es definitivamente una buena idea para un caso de prueba, agregaré uno mañana.Haskell ,
327360418394 bytesPruébalo en línea!
¡Fue muy divertido escribirlo! Primero
f
aparece la función y elimina todas las barras diagonales inversas al final de las líneas, luego lalines
divide en una lista de cadenas en las nuevas líneas. Luego mapeamos un montón de funciones en las líneas y las concatenamos todas juntas. Esas funciones: eliminar espacios en blanco desde la izquierda (t
) y desde la derecha (r.t.r
donder
estáreverse
); eliminar espacios en blanco del medio, ignorando cadenas y literales de caracteres, así como eliminar comentarios (w
); y finalmente agrega un carácter de nueva línea al final si la línea comienza con un #. Después de que todas las líneas se concatenan nuevamente, seg
buscan # caracteres y se asegura de que estén precedidos por una nueva línea.w
es un poco complejo, así que lo explicaré más a fondo. Primero verifico "//" ya que enw
Sé que no estoy en un literal de cadena Sé que este es un comentario, así que elimino el resto de la línea. A continuación, verifico si la cabeza es un delimitador para una cadena o literal de caracteres. Si es así, lo antepongo y paso el testigo all
que atraviesan los personajes, siguiendo el estado de "escape" con eln
que será cierto si ha habido un número par de barras consecutivas. Cuandol
detecta un delimitador y no está en el estado de escape, vuelve a pasar el testigow
, recortando para eliminar espacios en blanco después del literal porquew
espera que el primer carácter no sea un espacio en blanco. Cuandow
no encuentra un delimitador, usa span para buscar espacios en blanco en la cola. Si hay alguno, comprueba si los personajes que lo rodean no pueden ponerse en contacto e inserta un espacio si es así. Luego vuelve a aparecer una vez finalizado el espacio en blanco. Si no había espacios en blanco, no se inserta ningún espacio y se mueve de todos modos.EDITAR: ¡Muchas gracias a @DLosc por señalar un error en mi programa que realmente me permitió acortarlo también! ¡Hurra por la coincidencia de patrones!
EDIT2: ¡Soy un idiota que no terminó de leer las especificaciones! Gracias de nuevo DLosc por señalar eso!
Edit3: Apenas notó algo molesto reducción del tipo que se convirtió
e=elem
enChar->[Char]->Bool
por alguna razón, rompiendo así ele[a,q]
. Tuve que agregar una firma de tipo para forzarla a ser correcta. ¿Alguien sabe cómo podría solucionar eso? Nunca he tenido este problema en Haskell antes. TIOEDIT4: solución rápida para el error @FelixPalmen me mostró. Podría intentar jugar golf más tarde cuando tenga algo de tiempo.
EDIT5: -24 bytes gracias a @Lynn! ¡Gracias! ¡No sabía que podía asignar cosas en el ámbito global utilizando la coincidencia de patrones como
n:c:z=...
si fuera realmente genial! También es una buena idea hacer un operador porelem
deseo. Pensé en eso.fuente
e x y=elem x y
(o inclusoe x=elem x
) resuelve tu problema. (Cambié el nombree
a un operador(!)
C,
497494490489 bytesComo estamos procesando C, ¡hagámoslo usando C! La función
f()
toma la entrada del puntero de caracteresp
y las salidas al punteroq
, y supone que la entrada está en ASCII:Suponemos que el archivo está bien formado: los literales de cadena y caracteres están cerrados, y si hay un comentario en la línea final, debe haber una nueva línea para cerrarlo.
Explicación
La versión previa al golf es solo un poco más legible, me temo:
Implementa una máquina de estados por recursión de cola. Las macros y variables auxiliares son
O
para o utputR
a r de entrada ead enr
V
para determinar los caracteres identificadores de v alid (desde!isalnum('_')
)p
yq
- punteros de E / S como se describer
- último carácter a r eads
- s reciente personaje no en espacios en blancot
- t ag cuando se trabaja en una directiva de preprocesadorNuestros estados son
a()
- código C normalb()
- cadena literalc()
- comentard()
- código C normal, después de leerr
e()
- secuencia de escapef()
- estado inicial (función principal)g()
- en espacios en blancoh()
- en los espacios en blanco - envío ag()
oi()
i()
- inmediatamente después del espacio en blanco - ¿necesitamos insertar un carácter de espacio?j()
- espacio en blanco inicial: nunca inserte un carácter de espacioPrograma de prueba
Esto produce
Limitación
Esto rompe definiciones como
quitando el espacio que separa el nombre de la expansión, dando
con un significado completamente diferente. Este caso está ausente de los conjuntos de prueba, por lo que no voy a abordarlo.
Sospecho que podría ser capaz de producir una versión más corta con una conversión in situ de varios pasos; podría intentarlo la próxima semana.
fuente
=
al final de la definición deO
y cambiar el espacio que sigue a cada llamadaO
en a=
.O'\\'
yO' '
ambos adquirieron un espacio.C,
705663640 bytes¡Gracias a @ Zacharý por jugar 40 bytes y gracias a @Nahuel Fouilleul por jugar 23 bytes!
Pruébalo en línea!
fuente
for(;W;C++){}
convertirsefor(;W;C++);
?Perl 5,
250 + 3 (-00n), 167 + 1 (-p) bytesPruébalo en línea
fuente
Python 2 ,
479456445434502497 bytesPruébalo en línea!
Editar: Se ha corregido para incluir
- -
,+ +
y/ *
fuente