tło
Do przesłania kodu do golfa w C potrzebuję narzędzia do przetwarzania. Podobnie jak w wielu innych językach, białe znaki są przeważnie nieistotne w źródle C (ale nie zawsze!) - nadal sprawiają, że kod jest znacznie bardziej zrozumiały dla ludzi. W pełni golfowy program C, który nie zawiera ani jednej nadmiarowej białej spacji, często jest ledwo czytelny.
Dlatego lubię pisać mój kod w C dla kodu golfowego, w tym białych znaków i czasami komentarzy, więc program zachowuje zrozumiałą strukturę podczas pisania. Ostatnim krokiem jest usunięcie wszystkich komentarzy i zbędnych białych znaków. Jest to żmudne i bezmyślne zadanie, które naprawdę powinien wykonać stażysta programu komputerowego.
Zadanie
Napisz program lub funkcję, która eliminuje komentarze i zbędne białe znaki z jakiegoś „wstępnie golfowego” źródła C zgodnie z następującymi zasadami:
- A
\
(ukośnik odwrotny) jako ostatni znak w linii jest kontynuacją linii . Jeśli to znajdziesz, musisz traktować następujący wiersz jako część tego samego wiersza logicznego (możesz na przykład całkowicie usunąć\
następny i następujący\n
(nowy wiersz) przed zrobieniem czegoś innego) - Komentarze będą używać tylko formatu jednowierszowego, zaczynając od
//
. Tak, aby je usunąć, zignorować resztę logicznej linii gdziekolwiek spotkać//
poza ciągiem znaków (patrz poniżej). - Znaki białych znaków to
(spacja),
\t
(tab) i\n
(nowa linia, więc tutaj koniec linii logicznej). Gdy znajdziesz sekwencję białych znaków, sprawdź otaczające ją znaki niebiałe. Jeśli
- oba są alfanumeryczne lub podkreślone (zakres
[a-zA-Z0-9_]
) lub - oba są
+
lub - oba są
-
lub - poprzedni jest,
/
a następny jest*
następnie zastąp sekwencję pojedynczym znakiem spacji (
).
W przeciwnym razie całkowicie wyeliminuj sekwencję.
Ta reguła ma kilka wyjątków :
- Dyrektywy preprocesora muszą pojawiać się w osobnych wierszach na wydruku. Dyrektywa preprocesora to linia zaczynająca się od
#
. - Wewnątrz literału łańcuchowego lub literału znakowego nie należy usuwać żadnych białych znaków. Dowolny
"
(podwójny cudzysłów) /'
(pojedynczy cudzysłów), który nie jest poprzedzony nieparzystą liczbą odwrotnych ukośników (\
), rozpoczyna lub kończy literał łańcuchowy / literał znakowy . Masz gwarancję, że literały ciągów i znaków kończą się w tym samym wierszu, w którym zaczęły. literałów ciągów i literałów znakowych nie można zagnieżdżać, więc'
wewnątrz literału ciągowego , jak również"
wewnątrz literału znakowego , nie ma żadnego specjalnego znaczenia.
- oba są alfanumeryczne lub podkreślone (zakres
Specyfikacja we / wy
Dane wejściowe i wyjściowe muszą być albo ciągami znaków (ciągami znaków), w tym znakami nowej linii, albo tablicami / listami ciągów znaków, które nie zawierają znaków nowej linii. Jeśli zdecydujesz się użyć tablic / list, każdy element reprezentuje linię, więc nowe linie są ukryte po każdym elemencie.
Możesz założyć, że dane wejściowe są poprawnym kodem źródłowym programu C. Oznacza to również, że zawiera tylko drukowalne znaki ASCII, tabulatory i znaki nowej linii. Niezdefiniowane zachowanie na nieprawidłowo wprowadzonych danych jest dozwolone.
Wiodące i końcowe białe znaki / puste linie są niedozwolone .
Przypadki testowe
Wejście
main() { printf("Hello, World!"); // hi }
wynik
main(){printf("Hello, World!");}
Wejście
#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)); }
wynik
#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));}
Wejście
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)); }
wynik
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));}
Wejście
x; #include <stdio.h> int main() { puts("hello // there"); }
wynik
x; #include<stdio.h> int main(){puts("hello // there");}
wejście (przykład z prawdziwego świata)
// 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(); } } }
wynik
#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();}}}
To jest golf golfowy , więc wygrywa najkrótsza (w bajtach) poprawna odpowiedź.
Odpowiedzi:
Pip ,
148135133138 bajtówBajty są liczone w CP-1252 , tak
¶
i·
to jeden bajt każda. Zauważ, że oczekuje to kodu C jako pojedynczego argumentu wiersza poleceń, który (w rzeczywistym wierszu poleceń) wymagałby użycia obfitych sekwencji specjalnych. O wiele łatwiej w Wypróbuj online!Wyjaśnienie nieco nie golfowej wersji
Kod wykonuje kilka operacji wymiany z kilkoma sztuczkami.
Kontynuacje odwrotnego ukośnika
Wszyscy
RM
występujemy dosłowny ciągto jest ukośnik odwrotny, po którym następuje nowa linia.
Literały ciągów i znaków
Używamy zamiennika wyrażenia regularnego z funkcją wywołania zwrotnego:
Wyrażenie regularne pasuje do pojedynczego lub podwójnego cudzysłowu, po którym następuje chciwość,
.*?
która pasuje do 0 lub więcej znaków, tak mało jak to możliwe. Mamy negatywny wygląd, aby upewnić się, że poprzednia postać nie była odwrotna; następnie dopasowujemy parzystą liczbę odwrotnych ukośników, a następnie ponownie ogranicznik otwarcia.Funkcja zwrotna przyjmuje literał ciąg / znak i przesuwa go na koniec listy
l
. Następnie zwraca znak zaczynający się od kodu znaku 192 (À
) i rosnący z każdym zastąpieniem literału. Tak więc kod jest transformowany w następujący sposób:Te znaki zastępcze z pewnością nie występują w kodzie źródłowym, co oznacza, że możemy je później jednoznacznie zastąpić.
Komentarze
Wyrażenia regularne dopasowują
//
plus wszystko aż do nowej linii i zastępuje jex
(ustawione na pusty ciąg).Dyrektywy preprocesora
Zawija przebiegi znaków innych niż nowa linia, zaczynając od znaku funta
¶
.Miejsca, których nie należy eliminować
Dużo się tu dzieje. Pierwsza część generuje tę listę wyrażeń regularnych do zastąpienia:
Zwróć uwagę na użycie lookaheads dopasować, na przykład, tylko
e
indefine P printf
. W ten sposób ten mecz nie zużywaP
, co oznacza, że może go użyć następny mecz.Generujemy tę listę wyrażeń regularnych poprzez mapowanie funkcji na listę, na której znajduje się lista
a funkcja robi to z każdym elementem:
Kiedy mamy nasze wyrażenia regularne, zastępujemy ich wystąpienia tą funkcją wywołania zwrotnego:
który zamienia ciąg białych znaków w każdym dopasowaniu na
·
.Eliminacja i czyszczenie białych znaków
Trzy kolejne zamiany zastępują pozostałe serie białych znaków (
w
) pustym ciągiem (x
), nowymi¶
znakami i·
spacją.Podstawienie wsteczne literałów ciągów i znaków
Tworzymy listę wszystkich znaków, których używaliśmy jako zamienników literałów, pobierając
192 + range(len(l))
i konwertując na znaki. Następnie możemy zastąpić każdy z nich odpowiednim literałem wl
.I to wszystko! Powstały ciąg jest automatycznie drukowany.
źródło
//
literału ciągu jest zdecydowanie dobrym pomysłem na przypadek testowy, dodam go jutro.Haskell ,
327360418394 bajtówWypróbuj online!
Pisanie to była świetna zabawa! Najpierw
f
pojawia się funkcja i usuwa wszystkie ukośniki odwrotne na końcu wierszy, a następnielines
dzieli je na listę ciągów znaków na nowej linii. Następnie odwzorowujemy kilka funkcji na linie i łączymy je wszystkie razem. Funkcje te: usuwanie białych znaków z lewej (t
) i z prawej (r.t.r
gdzier
jestreverse
); usuń białe znaki ze środka, ignorując literały ciągów i znaków, a także usuwając komentarze (w
); i na końcu dodaje znak nowej linii na końcu, jeśli linia zaczyna się od #. Po połączeniu wszystkich linii z powrotemg
szuka znaków # i upewnia się, że poprzedza je nowa linia.w
jest trochę skomplikowany, więc wyjaśnię to dokładniej. Najpierw sprawdzam „//”, ponieważw
wiem, że nie jestem literałem ciągiem, wiem, że to komentarz, więc zostawiam resztę wiersza. Następnie sprawdzam, czy głowa jest ogranicznikiem literału łańcucha lub znaku. Jeśli tak, przygotowuję go i przekazuję pałeczkę, dol
której biegną postacie, śledząc stan „ucieczki”, zn
którym będzie to prawdą, jeśli wystąpi parzysta liczba kolejnych cięć. Kiedyl
wykryje ogranicznik i nie znajduje się w stanie ucieczki, przekazuje pałeczkę z powrotemw
, przycinając w celu wyeliminowania białych znaków po literałach, ponieważw
oczekuje, że pierwszy znak nie będzie białymi znakami. Kiedyw
nie znajduje separatora, używa span do szukania białych znaków w ogonie. Jeśli istnieje, sprawdza, czy nie można nawiązać kontaktu z otaczającymi go postaciami, i jeśli tak, wstawia spację. Następnie powtarza się po zakończeniu spacji. Jeśli nie było białych znaków, nie jest wstawiana spacja i i tak się przesuwa.EDYCJA: Bardzo dziękuję @DLosc za wskazanie błędu w moim programie, który faktycznie doprowadził mnie do tego, że mogę go również skrócić! Brawo dla dopasowania wzoru!
EDIT2: Jestem idiotą, który nie skończył czytać specyfikacji! Jeszcze raz dziękuję DLosc za wskazanie tego!
EDYCJA 3: Właśnie zauważyłem jakąś irytującą rzecz polegającą
e=elem
na redukcji typu, któraChar->[Char]->Bool
z jakiegoś powodu zmieniła się , powodując awarięe[a,q]
. Musiałem dodać podpis typu, aby wymusić poprawność. Czy ktoś wie, jak to naprawić? Nigdy wcześniej nie miałem tego problemu w Haskell. TIOEDIT4: pokazała mi szybka poprawka dla błędu @ FelixPalmen. Mogę spróbować zagrać w golfa później, kiedy będę miał trochę czasu.
EDIT5: -24 bajtów dzięki @Lynn! Dziękuję Ci! Nie wiedziałem, że możesz przypisywać rzeczy do globalnego zasięgu za pomocą dopasowywania wzorców, jakby
n:c:z=...
to było naprawdę fajne! Również dobrym pomysłem jest stworzenie operatora naelem
życzenie, o którym myślałem.źródło
e x y=elem x y
(a nawete x=elem x
) rozwiązuje problem. (Zmieniłem nazwęe
na operatora(!)
.)C,
497494490489 bajtówPonieważ przetwarzamy C, zróbmy to za pomocą C! Funkcja
f()
pobiera dane wejściowe ze wskaźnika charp
i dane wyjściowe do wskaźnikaq
i zakłada, że dane wejściowe są w ASCII:Zakładamy, że plik jest poprawnie sformułowany - literały ciągów i znaków są zamknięte, a jeśli w ostatnim wierszu jest komentarz, musi być nowy wiersz, aby go zamknąć.
Wyjaśnienie
Obawiam się, że wersja z golfem jest tylko trochę bardziej czytelna:
Implementuje maszynę stanu przez rekurencję ogona. Makra pomocnicze i zmienne są
O
o O utputR
do R EaD wejścia dor
V
w celu ustalenia v różnych znaków identyfikatora (od!isalnum('_')
)p
orazq
- wskaźniki I / O, jak opisanor
- ostatni znak być r EADs
- s ostatni niedawny znak spacjit
- t ag podczas pracy nad dyrektywą preprocesoraNasze stany są
a()
- normalny kod C.b()
- dosłowny ciąg znakówc()
- komentarzd()
- normalny kod C po odczytaniur
e()
- sekwencja ewakuacyjnaf()
- stan początkowy (główna funkcja)g()
- w białych znakachh()
- w białych znakach - wysyłka dog()
lubi()
i()
- natychmiast po spacji - czy musimy wstawić znak spacji?j()
- początkowa biała spacja - nigdy nie wstawiaj spacjiProgram testowy
To produkuje
Ograniczenie
To łamie definicje takie jak
usuwając spację oddzielającą nazwę od rozwinięcia, dając
o zupełnie innym znaczeniu. Ten przypadek jest nieobecny w zestawach testowych, więc nie zamierzam się nim zajmować.
Podejrzewam, że być może uda mi się stworzyć krótszą wersję z wieloprzebiegową konwersją lokalną - mógłbym spróbować w przyszłym tygodniu.
źródło
=
na końcu definicjiO
i zmieniając miejsce po każdym wywołaniuO
na=
.O'\\'
iO' '
oba uzyskały spację.C
705663640 bajtówDzięki @ Zacharý za grę w golfa 40 bajtów i dzięki @Nahuel Fouilleul za grę w golfa 23 bajty!
Wypróbuj online!
źródło
for(;W;C++){}
zostaćfor(;W;C++);
?Perl 5,
250 + 3 (-00n), 167 + 1 (-p) bajtówWypróbuj online
źródło
Python 2 ,
479456445434502497 bajtówWypróbuj online!
Edycja: Fixed zawierać
- -
,+ +
oraz/ *
źródło