Фон
Для моего представления кода в гольф на C мне нужен инструмент обработки. Как и во многих других языках, пробел в основном не имеет отношения к исходному тексту на Си (но не всегда!) - все еще делает код намного более понятным для людей. Полностью играющая в гольф C-программа, которая не содержит ни одного лишнего пробела, часто едва читаема.
Поэтому я люблю писать свой код на C для отправки кода в гольф, включая пробелы и иногда комментарии, чтобы программа сохраняла понятную структуру при написании. Последний шаг - удалить все комментарии и лишние пробелы. Это утомительно и бессмысленная задача , которая на самом деле должно быть сделано стажером компьютерной программой.
задача
Напишите программу или функцию, которая исключает комментарии и лишние пробелы из какого-либо «предварительно сыгранного» источника C в соответствии со следующими правилами:
\
(Обратный слэш) в качестве последнего символа в строке является продолжением строки . Если вы обнаружите это, вы должны обработать следующую строку как часть одной и той же логической строки (например, вы можете полностью удалить\
следующие\n
символы и следующую (новую строку ), прежде чем делать что-либо еще)- Комментарии будут использовать только однострочный формат, начиная с
//
. Таким образом, чтобы удалить их, вы игнорируете остальную часть логической строки, где бы вы ни находились,//
вне строкового литерала (см. Ниже). - Символами
пробела являются (пробел),
\t
(табуляция) и\n
(перевод строки, поэтому здесь конец логической строки). Когда вы найдете последовательность пробелов, изучите непробельные символы, окружающие ее. Если
- оба они являются буквенно-цифровыми или подчеркивания (диапазон
[a-zA-Z0-9_]
) или - оба являются
+
или - оба являются
-
или - предыдущий
/
и следующий*
затем замените последовательность одним
символом пробела ( ).
В противном случае полностью исключите последовательность.
Это правило имеет некоторые исключения :
- Директивы препроцессора должны появляться в своих строках в выходных данных. Директива препроцессора - это строка, начинающаяся с
#
. - Внутри строкового литерала или символьного литерала вы не должны удалять пробелы. Любой
"
(двойная кавычка) /'
(одинарная кавычка), которому непосредственно не предшествует нечетное число обратных косых черт (\
), начинает или заканчивает строковый литерал / символьный литерал . Вам гарантировано, что строковые и символьные литералы заканчиваются на той же строке, с которой они начинались.Строковые литералы и символьные литералы не могут быть вложенными, поэтому'
внутренний строковый литерал , а также"
внутренний символьный литерал не имеют никакого особого значения.
- оба они являются буквенно-цифровыми или подчеркивания (диапазон
Спецификация ввода / вывода
Входные и выходные данные должны представлять собой либо последовательности символов (строки), включая символы новой строки, либо массивы / списки строк, которые не содержат символов новой строки. Если вы решите использовать массивы / списки, каждый элемент представляет собой линию, поэтому перевод строки неявно после каждого элемента.
Вы можете предположить, что ввод является допустимым исходным кодом C-программы. Это также означает, что он содержит только печатные символы ASCII, вкладки и переводы строк. Неопределенное поведение при неправильном вводе допускается.
Ведущие и ведомые пробелы / пустые строки являются не допускаются .
Контрольные примеры
вход
main() { printf("Hello, World!"); // hi }
выход
main(){printf("Hello, World!");}
вход
#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)); }
выход
#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));}
вход
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)); }
выход
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));}
вход
x; #include <stdio.h> int main() { puts("hello // there"); }
выход
x; #include<stdio.h> int main(){puts("hello // there");}
вход (реальный пример)
// 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(); } } }
выход
#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();}}}
Это код-гольф , поэтому самый короткий (в байтах) правильный ответ выигрывает.
Ответы:
Пип ,
148135133138 байтБайты учитываются в CP-1252 , поэтому
¶
и·
составляют по одному байту каждый. Обратите внимание, что это предполагает, что код C является единственным аргументом командной строки, который (в реальной командной строке) потребует использования обильных escape-последовательностей. Попробовать онлайн гораздо проще !Объяснение версии слегка безглого
Код выполняет кучу операций замещения с парой трюков.
Продолжение с обратной косой чертой
У нас
RM
все вхождения буквальной строкито есть обратная косая черта с последующим переводом строки.
Строковые и символьные литералы
Мы используем замену регулярного выражения с функцией обратного вызова:
Регулярное выражение соответствует одинарной или двойной кавычке, за которой следует не жадный
.*?
который соответствует 0 или более символов, как можно меньше. У нас есть негативный взгляд на то, чтобы предыдущий символ не был обратной косой чертой; затем мы сопоставляем четное число обратных косых черт, за которыми снова следует разделитель.Функция обратного вызова берет строковый / символьный литерал и помещает его в конец списка
l
. Затем он возвращает символ, начинающийся с кода символа 192 (À
) и увеличивающийся с каждым замененным литералом. Таким образом, код преобразуется так:Эти замещающие символы гарантированно не встречаются в исходном коде, что означает, что мы можем однозначно заменить их позже.
Комментарии
Регулярное совпадение
//
плюс все до новой строки и заменяется наx
(предустановлено пустой строкой).Директивы препроцессора
Обтекания запускаются без символов перевода строки, начинающихся со знака решетки
¶
.Пространства, которые не должны быть устранены
Здесь много чего происходит. Первая часть генерирует этот список регулярных выражений для замены:
Обратите внимание на использование lookaheads для соответствия, например, только
e
indefine P printf
. Таким образом, это совпадение не используетP
, что означает, что следующее совпадение может использовать его.Мы генерируем этот список регулярных выражений, отображая функцию в список, где список содержит
и функция делает это с каждым элементом:
Получив наши регулярные выражения, мы заменим их вхождения этой функцией обратного вызова:
который заменяет пробел в каждом матче на
·
.Устранение и очистка пробелов
Три последовательные замены заменяют оставшиеся прогоны пробела (
w
) на пустую строку (x
), прогоны¶
символа новой строки и·
пробела.Обратная замена строковых и символьных литералов
Мы строим список всех символов, которые мы использовали в качестве замены для литералов, беря
192 + range(len(l))
и преобразовывая в символы. Затем мы можем заменить каждый из них соответствующим литералом вl
.Вот и все! Полученная строка автоматически печатается.
источник
//
внутреннего строкового литерала - определенно хорошая идея для тестового случая, я добавлю его завтра.Haskell ,
327360418394 байтаПопробуйте онлайн!
Это было очень весело писать! Сначала
f
функция проходит и удаляет все обратные слеши в конце строк, а затемlines
разбивает ее на список строк в новых строках. Затем мы отображаем несколько функций на линии и объединяем их все вместе. Эти функции: убрать пробелы слева (t
) и справа (r.t.r
гдеr
естьreverse
); удалить пробел из середины, игнорируя строковые и символьные литералы, а также удаляя комментарии (w
); и, наконец, добавляет символ новой строки в конец, если строка начинается с #. После того, как все строки соединены вместе,g
ищет символы # и гарантирует, что им предшествует символ новой строки.w
немного сложнее, поэтому я объясню это дальше. Сначала я проверяю «//», так какw
я знаю, что не в строковом литерале, я знаю, что это комментарий, поэтому я отбрасываю оставшуюся часть строки. Затем я проверяю, является ли заголовок разделителем строки или символьного литерала. Если это так, я добавляю его и передаю эстафету,l
которой управляют персонажи, отслеживая состояние «выхода», сn
которым будет верно, если было четное количество последовательных слешей. Когдаl
обнаруживает разделитель и не находится в состоянии перехода, он передает эстафету обратноw
, обрезая, чтобы исключить пробел после литерала, потому чтоw
ожидает, что первый символ не будет пробелом. когдаw
не находит разделитель, он использует span для поиска пробелов в хвосте. Если он есть, он проверяет, нельзя ли привести символы вокруг него в контакт, и вставляет пробел, если так. Затем это повторяется после того, как пропущен пробел. Если пробелов не было, пробел не вставляется, и он все равно перемещается.РЕДАКТИРОВАТЬ: Большое спасибо @DLosc за указание на ошибку в моей программе, которая фактически привела к тому, что я тоже смог ее сократить! Ура для сопоставления с образцом!
EDIT2: я идиот, который не закончил читать спецификации! Еще раз спасибо DLosc за то, что указал на это!
EDIT3: Просто заметил некоторые раздражающие сокращения типа вещь , которая превратилась
e=elem
вChar->[Char]->Bool
по какой - то причине, что нарушения наe[a,q]
. Мне пришлось добавить сигнатуру типа, чтобы она была правильной. Кто-нибудь знает, как я мог это исправить? У меня никогда не было этой проблемы в Хаскеле. TIOEDIT4: быстрое исправление ошибки @FelixPalmen показал мне. Я мог бы попытаться сыграть в гольф позже, когда у меня будет время.
EDIT5: -24 байта благодаря @ Линн! Спасибо! Я не знал, что вы можете назначить вещи в глобальной области видимости, используя сопоставление с образцом, как
n:c:z=...
это действительно круто! Также хорошая идея сделать оператора дляelem
желания, я бы подумал об этом.источник
e x y=elem x y
(или дажеe x=elem x
) решает вашу проблему. (Я переименовалe
в оператора(!)
.)C
497494490489 байтПоскольку мы обрабатываем C, давайте сделаем это с помощью C! Функция
f()
принимает входные данные от указателя символаp
и выводит указательq
, и предполагает, что вход находится в ASCII:Мы предполагаем, что файл правильно сформирован - строковые и символьные литералы закрыты, и если в заключительной строке есть комментарий, должна быть новая строка, чтобы закрыть его.
объяснение
Боюсь, что версия для игры в гольф немного более разборчива:
Он реализует конечный автомат с помощью хвостовой рекурсии. Вспомогательные макросы и переменные
O
для о utputR
чтобы R EAD ввода вr
V
для определения V Алид символов идентификатора (с!isalnum('_')
)p
иq
- указатели ввода / вывода, как описаноr
- последний символ будет г Свинецs
- S aved недавнего характера непробельногоt
- т аг при работе над директивой препроцессораНаши штаты
a()
- нормальный код Cb()
- строковый литералc()
- комментарийd()
- нормальный код C, после прочтенияr
e()
- escape-последовательностьf()
- начальное состояние (основная функция)g()
- в пробелахh()
- в пустом месте - отправкаg()
илиi()
i()
- сразу после пробела - нам нужно вставить пробел?j()
- начальный пробел - никогда не вставлять пробелТестовая программа
Это производит
ограничение
Это нарушает определения, такие как
удаляя пробел, отделяющий имя от расширения, давая
с совершенно другим значением. Этот случай отсутствует в тестовых наборах, поэтому я не буду его рассматривать.
Я подозреваю, что смогу создать более короткую версию с многоходовым преобразованием на месте - я мог бы попробовать это на следующей неделе.
источник
=
в конце определенияO
и изменив пространство, следующее за каждым вызовом,O
на a=
.O'\\'
иO' '
оба получили пробел.C
705663640 байтСпасибо @ Zacharý за гольф 40 байтов и благодаря @Nahuel Fouilleul за гольф 23 байта!
Попробуйте онлайн!
источник
for(;W;C++){}
статьfor(;W;C++);
?Perl 5,
250 + 3 (-00n), 167 + 1 (-p) байтовПопробуйте онлайн
источник
Python 2 ,
479456445434502497 байтПопробуйте онлайн!
Edit: Фиксированный включить
- -
,+ +
и/ *
источник