Hintergrund
Für meine Code-Golf- Einsendungen in C benötige ich ein Verarbeitungswerkzeug. Wie in vielen anderen Sprachen ist Whitespace in C-Quellen meistens irrelevant (aber nicht immer!) - macht den Code dennoch für den Menschen viel verständlicher. Ein vollwertiges C-Programm, das häufig kein redundantes Leerzeichen enthält, ist kaum lesbar.
Aus diesem Grund schreibe ich meinen Code gerne in C für eine Code-Golf- Einreichung mit Leerzeichen und manchmal Kommentaren, damit das Programm beim Schreiben eine nachvollziehbare Struktur beibehält. Der letzte Schritt besteht darin, alle Kommentare und überflüssigen Leerzeichen zu entfernen. Dies ist eine mühsame und sinnlose Aufgabe, die eigentlich ein Praktikant eines Computerprogramms erledigen sollte .
Aufgabe
Schreiben Sie ein Programm oder eine Funktion, die Kommentare und überflüssige Leerzeichen aus einer C-Quelle "vor dem Golfen" gemäß den folgenden Regeln entfernt:
- Ein
\
(Backslash) als letztes Zeichen in einer Zeile ist eine Zeilenfortsetzung . Wenn Sie dies feststellen, müssen Sie die folgende Zeile als Teil derselben logischen Zeile behandeln (Sie können beispielsweise die\
und die folgenden\n
Zeilen (newline) vollständig entfernen, bevor Sie etwas anderes tun). - Kommentare verwenden nur das einzeilige Format, beginnend mit
//
. Um sie zu entfernen, ignorieren Sie den Rest der logischen Zeile, wo immer Sie//
außerhalb eines Zeichenfolgenliterals vorfinden (siehe unten). Leerzeichen sind (Leerzeichen),
\t
(Tabulator) und\n
(Zeilenvorschub, hier also das Ende einer logischen Zeile).Wenn Sie eine Folge von Leerzeichen finden, untersuchen Sie die Nicht-Leerzeichen-Zeichen, die sie umgeben. Wenn
- beide sind alphanumerisch oder unterstrichen (Range
[a-zA-Z0-9_]
) oder - beide sind
+
oder - beide sind
-
oder - das vorhergehende ist
/
und das folgende ist*
Ersetzen Sie dann die Sequenz durch ein einzelnes Leerzeichen (
).
Andernfalls beseitigen Sie die Reihenfolge vollständig.
Diese Regel hat einige Ausnahmen :
- Präprozessor-Direktiven müssen in Ihrer Ausgabe in eigenen Zeilen erscheinen. Eine Präprozessor-Direktive ist eine Zeile, die mit beginnt
#
. - In einem String-Literal oder Zeichen-Literal sollten Sie keine Leerzeichen entfernen. Jedes
"
(doppelte Anführungszeichen) /'
(einfache Anführungszeichen), dem keine ungerade Anzahl von umgekehrten Schrägstrichen (\
) vorangestellt ist, beginnt oder endet ein Zeichenkettenliteral / Zeichenliteral . Es ist garantiert, dass Zeichenketten- und Zeichenliterale in derselben Zeile enden, in der sie begonnen haben. Stringliterale und Zeichenliterale können nicht verschachtelt werden, so dass ein'
in einem Stringliteral , sowie ein"
in einem Zeichenliteral haben keine besondere Bedeutung.
- beide sind alphanumerisch oder unterstrichen (Range
E / A-Spezifikation
Eingabe und Ausgabe müssen entweder Zeichenfolgen (Strings) einschließlich Zeilenumbrüchen oder Arrays / Listen von Strings sein, die keine Zeilenumbrüche enthalten. Wenn Sie Arrays / Listen verwenden, stellt jedes Element eine Zeile dar, sodass die Zeilenumbrüche nach jedem Element implizit sind.
Sie können davon ausgehen, dass die Eingabe ein gültiger C-Programm-Quellcode ist. Dies bedeutet auch, dass es nur druckbare ASCII-Zeichen, Tabulatoren und Zeilenumbrüche enthält. Undefiniertes Verhalten bei fehlerhafter Eingabe ist zulässig.
Führende und nachfolgende Leerzeichen / Leerzeilen sind nicht erlaubt .
Testfälle
Eingang
main() { printf("Hello, World!"); // hi }
Ausgabe
main(){printf("Hello, World!");}
Eingang
#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)); }
Ausgabe
#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));}
Eingang
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)); }
Ausgabe
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));}
Eingang
x; #include <stdio.h> int main() { puts("hello // there"); }
Ausgabe
x; #include<stdio.h> int main(){puts("hello // there");}
Eingabe (ein Beispiel aus der Praxis)
// 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(); } } }
Ausgabe
#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();}}}
Dies ist Code-Golf , also gewinnt die kürzeste (in Bytes) gültige Antwort.
Antworten:
Pip ,
148135133138 BytesBytes werden gezählt , in CP-1252 , so
¶
und·
sind jeweils ein Byte. Beachten Sie, dass dies den C-Code als einzelnes Befehlszeilenargument erwartet, das (in einer tatsächlichen Befehlszeile) die Verwendung zahlreicher Escape-Sequenzen erfordert. Bei Try it online ist es viel einfacher !Erklärung der leicht ungolften Version
Der Code führt eine Reihe von Ersetzungsvorgängen mit ein paar Tricks aus.
Backslash-Fortsetzungen
Wir
RM
alle Vorkommen der wörtlichen ZeichenfolgeDas heißt, Backslash, gefolgt von Newline.
Zeichenketten- und Zeichenliterale
Wir verwenden einen Regex-Ersatz mit einer Rückruffunktion:
Der reguläre Ausdruck entspricht einem einfachen oder doppelten Anführungszeichen, gefolgt von einem nicht gierigen Ausdruck
.*?
, der 0 oder mehr Zeichen entspricht, so wenig wie möglich. Wir haben ein negatives Aussehen, um sicherzustellen, dass das vorherige Zeichen kein Backslash war. dann passen wir eine gerade Anzahl von Backslashes an, gefolgt vom öffnenden Begrenzer.Die Rückruffunktion nimmt das Zeichen / Zeichen-Literal und verschiebt es an den Ende der Liste
l
. Anschließend wird ein Zeichen zurückgegeben, das mit dem Zeichencode 192 (À
) beginnt und mit jedem ersetzten Literal zunimmt. So wird Code wie folgt transformiert:Es wird garantiert, dass diese Ersatzzeichen im Quellcode nicht vorkommen, was bedeutet, dass wir sie später eindeutig zurücksetzen können.
Bemerkungen
Der
//
reguläre Ausdruck stimmt mit allem bis zum Zeilenumbruch überein und wird durchx
(voreingestellt auf den leeren String) ersetzt.Präprozessor-Direktiven
Zeilenumbrüche von Zeichen, die keine Zeilenumbrüche sind, beginnen mit einem Doppelkreuz
¶
.Leerzeichen, die nicht beseitigt werden sollten
Hier ist viel los. Der erste Teil generiert diese Liste der zu ersetzenden regulären Ausdrücke:
Beachten Sie die Verwendung von Lookaheads anzupassen, beispielsweise nur die
e
indefine P printf
. Auf diese Weise verbraucht dieses Match nicht dasP
, was bedeutet, dass das nächste Match es verwenden kann.Wir generieren diese Liste der regulären Ausdrücke, indem wir eine Funktion einer Liste zuordnen, in der die Liste enthalten ist
und die Funktion tut dies für jedes Element:
Sobald wir unsere regulären Ausdrücke haben, ersetzen wir ihre Vorkommen durch diese Rückruffunktion:
Das ersetzt die Leerzeichenfolge in jedem Spiel mit
·
.Leerzeichen entfernen und bereinigen
Drei aufeinanderfolgende Ersetzungen ersetzen die verbleibenden Leerzeichenfolgen (
w
) für leere Zeichenfolgen (x
),¶
Zeilenumbrüche (Newline) und·
Leerzeichen.Rückersetzung von Zeichenketten- und Zeichenliteralen
Wir erstellen eine Liste aller Zeichen, die wir als Ersetzungen für Literale verwendet haben, indem wir Zeichen nehmen
192 + range(len(l))
und in Zeichen konvertieren. Wir können dann jedes dieser Elemente durch das zugehörige Literal in ersetzenl
.Und das ist es! Die resultierende Zeichenfolge wird automatisch gedruckt.
quelle
//
Strings in ein Literal ist definitiv eine gute Idee für einen Testfall, ich werde morgen eines hinzufügen.Haskell ,
327360418394 BytesProbieren Sie es online!
Es hat viel Spaß gemacht, dies zu schreiben! Zuerst kommt die
f
Funktion durch und entfernt alle Backslashes am Ende der Zeilen, dannlines
zerlegt sie sie in eine Liste von Strings an den Zeilenumbrüchen. Dann ordnen wir den Zeilen eine Reihe von Funktionen zu und verknüpfen sie wieder miteinander. Diese Funktionen: Leerzeichen von links (t
) und von rechts (r.t.r
wor
istreverse
) entfernen ; Leerzeichen aus der Mitte entfernen, Zeichenketten- und Zeichenliterale ignorieren sowie Kommentare entfernen (w
); und fügt am Ende ein Zeilenumbruchzeichen hinzu, wenn die Zeile mit einem # beginnt. Nachdem alle Zeilen wieder zusammengefügt wurden, wirdg
nach # Zeichen gesucht und sichergestellt, dass vor ihnen eine neue Zeile steht.w
ist ein wenig komplex, deshalb erkläre ich es weiter. Zuerst überprüfe ich auf "//", daw
ich in weiß, dass ich kein String-Literal bin. Ich weiß, dass dies ein Kommentar ist, also lasse ich den Rest der Zeile fallen. Als nächstes überprüfe ich, ob der Kopf ein Begrenzer für eine Zeichenkette oder ein Zeichenliteral ist. Wenn es so ist, stelle ich es voran und übergebe den Taktstock,l
der durch die Charaktere läuft, wobei ich den "Escape" -Zustand verfolge, dern
wahr ist, wenn es eine gerade Anzahl von aufeinanderfolgenden Schrägstrichen gegeben hat. Wennl
ein Trennzeichen erkannt wird und es sich nicht im Escape-Status befindet, wird der Taktstock an zurückgegebenw
, um Leerzeichen nach dem Literalw
zu entfernen, da erwartet wird, dass das erste Zeichen kein Leerzeichen ist. Wannw
findet kein Trennzeichen, das span verwendet, um nach Leerzeichen im Schwanz zu suchen. Wenn es welche gibt, prüft es, ob die Charaktere um es herum nicht in Kontakt gebracht werden können und fügt ein Leerzeichen ein, wenn dies der Fall ist. Dann wiederholt es sich, nachdem das Leerzeichen beendet ist. Wenn es kein Leerzeichen gab, wird kein Leerzeichen eingefügt und es geht trotzdem weiter.EDIT: Vielen Dank an @DLosc für den Hinweis auf einen Fehler in meinem Programm, der dazu geführt hat, dass ich ihn auch verkürzen konnte! Ein Glück für den Pattern Matching!
EDIT2: Ich bin ein Idiot, der die Spezifikation nicht zu Ende gelesen hat! Nochmals vielen Dank, DLosc, dass Sie darauf hingewiesen haben!
EDIT3: Habe gerade eine nervige Typreduktionssache bemerkt, die sich aus irgendeinem Grund
e=elem
in eine solche verwandelt hatChar->[Char]->Bool
und damit aufbrache[a,q]
. Ich musste eine Typensignatur hinzufügen, um die Richtigkeit zu erzwingen. Weiß jemand, wie ich das beheben könnte? Ich hatte dieses Problem noch nie in Haskell. TIOEDIT4: Schnellkorrektur für Fehler @FelixPalmen zeigte mir. Ich könnte versuchen, es später zu spielen, wenn ich etwas Zeit habe.
EDIT5: -24 Bytes dank @Lynn! Vielen Dank! Ich wusste nicht, dass man Dinge auf globaler Ebene mit Pattern Matching zuordnen kann, wie
n:c:z=...
das wirklich cool ist! Auch eine gute Idee, einen Operator für denelem
Wunsch zu machen, an den ich gedacht hatte.quelle
e x y=elem x y
(oder sogare x=elem x
) löst Ihr Problem. (Ich wurde ine
einen Operator umbenannt(!)
.)C
497494490489 BytesDa wir C verarbeiten, machen wir es mit C! Die Funktion
f()
nimmt Eingaben vom Zeichenzeigerp
und Ausgaben vom Zeichenzeiger anq
und geht davon aus, dass die Eingabe in ASCII erfolgt:Wir gehen davon aus, dass die Datei wohlgeformt ist - Zeichenketten- und Zeichenliterale sind geschlossen, und wenn die letzte Zeile einen Kommentar enthält, muss eine neue Zeile eingefügt werden, um sie zu schließen.
Erläuterung
Die Pre-Golf-Version ist leider nur geringfügig besser lesbar:
Es implementiert eine Zustandsmaschine durch Schwanzrekursion. Die Hilfsmakros und Variablen sind
O
für o utputR
zu r ead Eingabe inr
V
um zu bestimmen , v alid Identifikator Zeichen (da!isalnum('_')
)p
undq
- E / A-Zeiger wie beschriebenr
- letztes Zeichen seine r eads
- s aved neuer Nicht-Whitespace-Charaktert
- t ag bei der Arbeit an einer Präprozessor-DirektiveUnsere Staaten sind
a()
- normaler C-Codeb()
- String-Literalc()
- Kommentard()
- normaler C-Code nach dem Lesenr
e()
- Fluchtabfolgef()
- Ausgangszustand (Hauptfunktion)g()
- In Leerzeichenh()
- im Leerzeichen - Versand ang()
oderi()
i()
- Unmittelbar nach dem Leerzeichen - müssen wir ein Leerzeichen einfügen?j()
- Anfängliches Leerzeichen - Fügen Sie niemals ein Leerzeichen einTestprogramm
Dies erzeugt
Verjährung
Dies bricht Definitionen wie
durch Entfernen des Leerzeichens, das den Namen von der Erweiterung trennt, geben
mit einer ganz anderen Bedeutung. Dieser Fall fehlt in den Testsätzen, daher werde ich ihn nicht ansprechen.
Ich vermute, dass ich mit einer In-Place-Konvertierung mit mehreren Durchläufen eine kürzere Version erstellen kann - das werde ich vielleicht nächste Woche versuchen.
quelle
=
am Ende der Definition von stehende Zeichen entfernenO
und das Leerzeichen, das jedem Aufruf folgt,O
in a ändern=
.O'\\'
undO' '
beide haben ein Leerzeichen erhalten.C
705663640 BytesDanke an @ Zacharý für das Golfen mit 40 Bytes und danke an @Nahuel Fouilleul für das Golfen mit 23 Bytes!
Probieren Sie es online!
quelle
for(;W;C++){}
werdenfor(;W;C++);
?Perl 5,
250 + 3 (-00n), 167 + 1 (-p) BytesProbieren Sie es online aus
quelle
Python 2 ,
479456445434502497 BytesProbieren Sie es online!
Edit: Feste zu schließen
- -
,+ +
und/ *
quelle