ACM ICPC 2006 Moscow Subregional Contest, Code Checker

Вопросы программирования на Free Pascal, использования компилятора и утилит.

Модератор: Модераторы

ACM ICPC 2006 Moscow Subregional Contest, Code Checker

Сообщение Mayor » 08.09.2007 18:47:27

а вот собственно, то для чего мне понадобился парсинг:

ACM ICPC 2006­2007, NEERC, Moscow Subregional Contest
Moscow, October 21, 2006
Problem C. Code Checker
Time limit:
3 seconds
Memory limit:
64 MiB
Prof. Acmeroff teaches programming in the Institute of Business Management. He teaches a relatively new,
but popular language called Db. Unfortunately, program plagiarism is a serious headache for the professor,
because some students cheat when doing home exersises. So the professor has to check students' works for
plagiatrism each time. Thus a computer program, which is able to perform some sort of program comparison
might be very useful for him.
The Db language is briefly described as follows:
· The # character never appears in programs. The character \ never appears in programs outside of
strings.
· All characters with codes less or equal then 32 are whitespace characters. A sequence of whitespace
characters is called whitespace and equivalent to one space character (code 32).
· Comments starting from /* run till the first sequence of */ characters.
· A comment is equivalent to one space character.
· String literals start from " and run till the next ", except \".
· An identifier is the longest possible sequence of characters starting from latin letter or underscore
and containing latin letters, underscore and digits. For example, sequence (0x800+) contains one
identifier x800. Letter case is significant.
· Some indentifiers are reserved as keywords. The exact list of keywords depends on the language
dialect.
· There are no lexical scoping rules.
A program is processed as follows:
1. Each comment is replaced with a single space character.
2. Each whitespace (including space characters from the previous step) is replaced with a signle space
character.
3. Each string is replaced with "".
4. Space characters are removed, except those separating identifiers and keywords from each other.
Finally, if there exists an one-to-one correspondence between the identifiers of the first program and the
indentifiers of the second program, so the replacement of the identifiers in the second program with the
corresponding identifiers of the first program yelds two equal texts, such programs are called identical.
Write a program, which checks whether two given programs written in Db are identical.
Input
The input for your program consists of the list of keywords and two programs written in Db. The list of
keywords and the programs are separated from each other with a single # character. The list of keywords
section only contains keywords and whitespace. The size of input does not exceed 4MiB. The total number
of different identifiers and keywords does not exceed 65535.
Page 4 of 17
ACM ICPC 2006­2007, NEERC, Moscow Subregional Contest
Moscow, October 21, 2006
Output
Print the word YES, if the input programs are identical, and the word NO otherwise.
Example
stdin
stdout
int
void
YES
while if return
#
/* sizeof(int) == 4 */
int main(void) {
int max = 0x80000000, n;
while(scanf("%d", &n) == 1)
if (n > max) max = n;
printf("%d", max);
return 0;
}
#
/* sizeof(int) == 4 */
int n(void) {
int max = 0x80000000, main;
while(printf("%d", &main) ==
1)
if(main > max)
max = main;
scanf("Answer: \"%d\"", max);
return 0;
}
Page 5 of 17




решение на паскале ( fpc ) :
Код: Выделить всё
program array1;
{$mode objfpc}
uses crt,sysutils;
   
type
   pget = function() : byte;
   pa = procedure(iii : byte);
   pliteral = function(var a : ansistring): boolean;
   endloop = class(exception);
   ss = set of char;
var
   dl,sid,id : ss;
   a1 : array [1..100] of string;
   bl : boolean;
   pl : pliteral;
   file1,file2 : AnsiString;
   tstr : string;
   wnum,knum : dword;  { number of indifier+keyword,keyword }
   pread : pget;
   padd : pa;
   iii,i,ii : dword;
   c : char;
   b : byte;
   loop : dword;
   f : file of byte;
procedure add1(b1 :byte); forward;
procedure add2(b1 :byte); forward;
function get2() : byte; forward;
function  checkend(b1 : byte) : boolean;
var
   temp : boolean;
begin
   temp   := ( filepos(f) >= filesize(f))  or ( b1 = byte( '#')) ;
   if temp then loop :=1;
   checkend := temp;
end;
function  get1() : byte;
begin
   read(f,b);
   checkend(b);
   if b>32 then
   begin
      pread := @get2;
      padd := @add2;
   end;

   get1 := b;
end;
function get2() : byte;
begin
   read(f,b);
   checkend(b);
   if b<33 then
   begin
      pread := @get1;
      padd := @add1;
      wnum += 1;
   end;
   get2 :=b;
end;
procedure add1(b1 :byte);
begin
   end;
procedure add2(b1 :byte);
begin
   tstr := char(b1);
   a1[wnum] += tstr;
end;
function l2(var a : ansistring) : boolean ; forward;
function l3(var a : ansistring) : boolean ; forward;
function l1(var a : ansistring) : boolean ;
begin
   i := length( a);
   bl := false;
   loop := 0;
   while ii<=i do
   
   begin
      if a[ii] = '"' then
      begin
         bl := true;
         ii+=1;
         loop := 1;
         pl := @l2;

         break;
      end;
      ii+=1;
   end;
   i := ii;
   l1 := bl;
end;


function l2(var a : ansistring) : boolean ;
begin
   if ( a[ii-1] = '\') then
else
   if (a[ii] = '"')  then pl:=@l3;
   ii+=1;
   l2:=true
   
end;

function l3(var a : ansistring) : boolean ;
begin
   delete(a,i,ii-1-i);
   pl := @l1;
   ii := i+1;
   l3:=true;
end;
function l5(var a : ansistring) : boolean ; forward;
function l7(var a : ansistring) : boolean ; forward;
function l4(var a : ansistring) : boolean ;
begin
   i+=1;
   l4 := false;
   if a[i]  in id then
   begin
      pl := @l7;
      if a[i] in sid then
      begin
            pl := @l5;{  id or kw begin }
      end;
   end;

   if i<length(a) then l4:= true;

end;

function l5(var a : ansistring) : boolean ;
         var tss : string;
begin
   tstr:='';
   while a[i] in id do
   begin
      tstr+=a[i];
      i+=1;
   end;
   iii:=0;
   for ii:=1 to wnum do
   begin
      if a1[ii] = tstr then
      begin

         iii:=ii;
         {
         writeln('keyword :',tstr,' id:',iii);
         }

         i-=length(tstr);
         delete(a,i,length(tstr));
         str(iii,tss);
         insert('\',tss,1);
         insert(tss,a,i);
         i+=length(tss);
         
         break;
      end;
   end;
   if iii = 0 then
   begin
      wnum+=1;
      a1[wnum] := tstr;

      i-=length(tstr);
      delete(a,i,length(tstr));
      str(wnum,tss);
      insert('\',tss,1);
      insert(tss,a,i);
      i+=length(tss);
      writeln('indifier id = ',wnum,' : ',tstr);
   end;
   pl := @l4;
   l5:=true;
end;

function l7(var a : ansistring) : boolean;
begin
   write('constant : ');
   while a[i] in id do
   begin
      write(a[i]);
      i+=1;
   end;
   writeln();
   pl:=@l4;
   l7:=true;
end;

procedure preformat( var a : ansistring);
label j1,j2,j3,j4;
begin
{ delete comments }
   goto j3;
   j4:
   delete(a,i,ii-i+2);
   j3:
   i:=pos('/*',a);
   ii:=pos('*/',a);
   if i*ii>0 then goto j4;

{ delete extra space }
   goto j1;
   j2:
   delete(a,i,1);
   j1:
   i:=pos('  ',a) ;
   if i>0 then goto j2;
{ delete literal }
   pl := @l1;
   loop := 1;
   ii := 1;
   while( loop > 0 ) do
   begin
      pl(a)  ;
   end;
end;

   { main }

begin
{ init }
   assign(f,'pcre.c');
   reset(f);
   dl := [' '..'/',':'..'@','['..'`','{'..'~'];
   sid := ['_','A'..'Z','a'..'z'];
   id := sid + ['0'..'9'];
{ read keywords }
   pread := @get1;
   padd := @add1;
   wnum :=1;
   while loop =0  do
   begin
   b := pread();
   padd(b);
   end;
   for loop := 1 to wnum do writeln('keyword id = ',loop,' : ',a1[loop]) ;
{ read 1 program }
   repeat
   read(f,b);
   if b<32 then b :=32;
   tstr := char(b);
   file1 += tstr;
   insert(' ',file1,1);
   until b = 35 ;
   delete(file1,length(file1),1);
   preformat(file1);
{ indifier parse }
   ii :=2;
   i := 1;
   pl :=@l4;
   writeln('creating first program indifiers ... ');
   knum:=wnum;
   while pl(file1) do ii+=1;
{ read 2 program }
   repeat
   read(f,b);
   if b<32 then b :=32;
   tstr := char(b);
   file2 += tstr;
   until filepos(f) >= filesize(f) ;
   insert(' ',file2,1);
   preformat(file2);
{ indifier parse}
   writeln('creating second program indifiers ... ');
    { for i:=knum to wnum do a1[i] := '';}
   wnum:=knum;
   ii :=2;
   i := 1;
   pl :=@l4;
   knum:=wnum;
   while pl(file2) do ii+=1;



   writeln(file2);
   writeln(file1);
   if file1 = file2 then writeln('programs equals');
end.


пример :
вход ::
Код: Выделить всё

printf  return    int
     if  then

#include <stdio.h>
/* coment 1 */123
include <pcre.h>
int main() {
   pcre *p;  /* comment 2 /  *   /
   sldkfls sdlfj;  ljkkj   sldkjflk */123
/   /
   printf("pcre: hello \" there\n");
   p=pcre_compile("lalalal",0,0,0,0);
   return 0;
#include <stdio.h>
/* coment 1 */123
include <pcre55.h>
int main() {
   pcre55 *p;  /* comment 2 /  *   /
   sldkfls sdlfj;  ljkkj   sldkjflk */123
/   /
   printf("pcre: sldkfjlk kk hello \" there\n");
   p=pcre_compile("lalalal",0,0,0,0);
   return 0;


выход:
Код: Выделить всё
keyword id = 1 : printf
keyword id = 2 : return
keyword id = 3 : int
keyword id = 4 : if
keyword id = 5 : then
keyword id = 6 : #
creating first program indifiers ...
indifier id = 7 : include
indifier id = 8 : stdio
indifier id = 9 : h
constant : 123
indifier id = 10 : pcre
indifier id = 11 : main
indifier id = 12 : p
constant : 123
indifier id = 13 : pcre_compile
constant : 0
constant : 0
constant : 0
constant : 0
constant : 0
creating second program indifiers ...
indifier id = 7 : include
indifier id = 8 : stdio
indifier id = 9 : h
constant : 123
indifier id = 10 : pcre55
indifier id = 11 : main
indifier id = 12 : p
constant : 123
indifier id = 13 : pcre_compile
constant : 0
constant : 0
constant : 0
constant : 0
constant : 0
\7 <\8.\9> 123 \7 <\10.\9> \3 \11() { \10 *\12; 123 / / \1(""); \12=\13("",0,0,

0,0); \2 0;
\7 <\8.\9> 123 \7 <\10.\9> \3 \11() { \10 *\12; 123 / / \1(""); \12=\13("",0,0,

0,0); \2 0;
programs equals


на решение ушло:

- 3 часа на поиск юнита регулярных выражений
- 30 мин на изучение libboost - как оказалось написано полностью на с++
- 5 часов на изучение и попытку написать врапер к pcre - потом решил делать все вручную без бибилиотек, будет больше практики с паскалем

- 3 часа на разработку алгоритма ( выбрал за основу теорию автоматов )
- 8 часов на его реализацию, пока не понял что утратил контроль над кодом, теория автоматов плохо реализуется на паскале, во всяком случае после 32 часового знакомсва с ним

- пришлось сделать откат кода на 5 часов назад
- 6.5 часов разработка нового алгоритма ( теорию автоматов пришлось заменить на императивный, а потом процедурный стиль )
- 12 часов на реализацию
- отладки почему-то опять не потребовалось ... может быть потому-что я разучился отлаживать ?

зы я фигею за человека, способного решить эту задачу в реале на чемпионате за 2 часа !!!

несколько пришлось отойти от класического задания, тк не умел делат несколько фишек :
1. тк не знаю как считать eof со стандартного ввода, данные прога считывает из файла pcre.c
2. тк не знаю как на паскале делать релизную и отладочную версии, то отладочная информация выдается на экран
3. тк встроенные средсва языка плохо работают ( или я в них плохо разобрался ) со строками, то из-за не эффективного алгоритма прога выходит за рамки времени при 10-30 кб исходниках, при норме в 1мб кода

ну естесно накопились вопросы:
- как посмотреть узкое место выполнения?
- на базе чего удобнее решать эту задачу: теория автоматов, data-driven или что то еще ?
- где можно что нить найти по теории автоматов и по теории формальных грамматик для ньюба?
Mayor
новенький
 
Сообщения: 20
Зарегистрирован: 04.09.2007 16:55:14

Сообщение shade » 08.09.2007 20:02:32

Mayor писал(а):зы я фигею за человека, способного решить эту задачу в реале на чемпионате за 2 часа !!!

Незная ничего о теории компиляции врядли кто решит задачу на прасинг сложнее чем парсинг и вычисление простого арифметического выражения...

Все эти соревнования ориентированны скорее на демонстрацию знаний/навыков/умений, а не как не сообразительности, находчевости, умения рещать задачи с которыми никогда не сталкивался. Побеждает тот, кто знает как решать (т.е. знает подходы к решению) большего числа типов задач...

А так, то что вы привели замеры времени - изучал то, пробовал другое...
Читать условие было влом, но судя по входным/выходным данным там лесический разбор с последующим сравнением порядка следования классов лексем...
Хорошо зная регулярные выражения и тот самый юнит вы бы решили эту задачу менее чем за полчаса.
Без регулярных выражений, с помощью конечного автомата - около часа или два - если где-то запутатесь и прийдется отлаживать...

Я наример писал посветку синтаксиса (правда на PHP) - там тоже лексический разбор - смысл разбить на лексемы - каждая лексема подсвечивается своим цветом. Описание читать тут:
выделение комментариев (Pascal) - решение на Pascal, описание скудное, т.к. задача проста
Форматирование текстов программ на Pascal в HTML формат - решение на PHP, описание помоему вполне подробное, можно протестировать скрипт в онлайн

Mayor писал(а):- на базе чего удобнее решать эту задачу: теория автоматов, data-driven или что то еще ?

1. Наверное на базе регулярных выражений,
2. или вчасти на базе таких утилит как lex, yacc. Кстати вместе с fpc идут утилиты plex, pyacc - см. каталог utils/tply в исхождниках fpc
3. На базе конечных автоматов, см. ссылки которые я приводил

Mayor писал(а):- где можно что нить найти по теории автоматов и по теории формальных грамматик для ньюба?

Загляни на intuit.ru там есть целый раздел посвященный теории компиляции, а дальще книжки... В частности вроде есть книжка Ахо, точное название не помню, но слово компиляция или компилятор там есть. На интуите скорее всего есть список литературы и там эта книжка должна быть упомянута, а также книжка Вирта, названия тоже не помню (Contructing Complier - вроде так на англцком, перевод вроде имеется). К сожалению мне эти книжки на руки не попадали, а так купил бы.

PS: могу обрадовать - в следующий раз вы решите подобную задачу значительно быстрее :wink:
Аватара пользователя
shade
энтузиаст
 
Сообщения: 879
Зарегистрирован: 21.02.2006 20:15:48
Откуда: http://shamangrad.net/

Сообщение shade » 08.09.2007 20:16:41

Вот в частности
Разработка компиляторов
Теория и реализация языков программирования
Литература, но упомянутых книжек я там не ображул, вообще ничего имеющего отнощения к теори комплиции

Ахо А.В., Сети Р., Ульман Д.Д. Компиляторы: принципы, технологии и инструменты

Книгу Вирта, не нашей и времени нет....
Аватара пользователя
shade
энтузиаст
 
Сообщения: 879
Зарегистрирован: 21.02.2006 20:15:48
Откуда: http://shamangrad.net/

Сообщение Mayor » 12.09.2007 14:28:11

dimka писал(а):
Код: Выделить всё
PROGRAM <имя программы>(Input, Output);
WHILE NOT Eof(Input) DO
   BEGIN
      Read(<буфер чтения>);
      <прочие действия>
   END;

И, сколь мне известно, работу с этими двумя файлами поддерживает любая версия, любой компилятор Pascal.


отлично -- просто у меня не было под рукой учебника, в справочнике такие тонкости видимо считаются общеизвестными и не упоминаются ...


dimka писал(а):В-третьих, я смотрю уже не первую тему в этом разделе, и постоянно удивляюсь, как можно судить об удобствах языков программирования, если не уметь проектировать (т.е. составлять внятные алгоритмы или модели)?


ну своего рода раш подход - смотришь на то, что можно на языке сделать после 20-50 часового знакомства и забываешь или продолжаешь изучение :)

dimka писал(а):В частности, я не понял, зачем для этой задачи использовать какой-либо сторонний парсер?


скорость написания программы, абстракция алгоритма

dimka писал(а):Также я не понял, почему на Pascal плохо пишутся автоматы? У нас даже студенты легко пишут автоматы в курсе о компиляторах на любых алгоритмических и объектно-ориентированных языках, в частности, на Pascal.


ну не знаю, теорию автоматов не читал, курс компиляторов тоже, с паскалем знаком 38 часов - наверное просто не уловил сути :)

хм а зачем теории автоматов, объектно-ориентированный язык?
в чем приемущества его использования?

dimka писал(а):В-чевёртых, при должной тренировке и грамотном разделении труда (в олимпиадной команде 3 участника) такая задача за пару часов решается, и тренер команды нашего университета, посмотрев эту тему, не нашёл в задаче ничего особо сложного.


там 11 задач на 5 часов, я почему то думал что эффективнее раскидать задачи по участникам, что бы они не мешали друг другу, чем заниматься организацией их работы ...


dimka писал(а):Я бы решал задачу методом конвейерной обработки, определив в качестве интерфейса между частями конвейера:
- передаваемый символ,
- управляющие работой конвейера сигналы.


я поначалу так же пытался, но код стал совершенно не поддерживаемый, алгоритм по ходу менялся тк не мог реализовать некоторые команды из-за слабого знания языка

а чем турбо паскаль так хорош?
он под линуксом есть?

dimka писал(а):2) На первом шаге работает анализатор блоков входной информации, который представляет собой простейший автомат, последовательно переходящий между состояниями "Ключевые слова", "Первая программа", "Вторая программа" в момент чтения разделительного символа #. Текущее состояние (текущий блок) и входной символ, кроме разделителя блоков, передаются далее по конвейеру для последующей обработки. Также в конвейер транслируется внешний сигнал о завершении работы и генерируется внутренний сигнал о смене режима работы (текущего блока).


когда код превысил 300 строк запутался в сигналах и переписал в императивном стиле 3 циклами : ключевые слова, 1 прога , 2 прога
до трансляции внешнего сигнала о завершении не додумался,
зато хотел сделать наоборот при получении # || eof внутренний блок расположенный за фильтром завершает работу и поднимает исключение, но не осилил работу с исключениями в паскале ...

dimka писал(а):3) На втором шаге работает фильтр, который анализирует входной поток, очищает его от комментариев, строковых констант и лишних пробельных символов, замещает любые пробельные символы на пробел и подаёт обработанные символы на выход. Для работы фильтра требуется буфер глубиной 2 символа, работающий по схеме FIFO, который принудительно выталкивается при получении сигналов заверешения работы и смены режима (текущего блока). На выход подаётся очередной символ и текущий блок обработки. Транслируются далее по конвейеру сигналы завершения работы и смены режима. Фильтр можно разложить на три последовательных фильтра: в начале комментариев, затем строковых констант и, в конце, пробельных символов. Каждый фильтр представляет собой простейший автомат, который опознаёт начало фильтруемой последовательности, и переходит в активное состояние; в нём он пропускает все символы, пока не будет опознан конец фильтруемой последовательности, после чего подаёт на выход замещающий последовательность символ (если нужно).


до использования буфера не додумался, хотел получить подобную функциональность regexpr, потом хотел собрать на автоматах единый фильтр, но когда возникли языковые трудности, в итоге все превратилось 3 цикла: коментарии, строки, пробелы ... и есть у меня такое чувство, что это и есть узкое место программы ... чем бы проверить ....

dimka писал(а):4) На третьем шаге действует анализатор идентификаторов. Для работы анализатора требуется буфер неопределённого размера, в котором посимвольно накапливается очередной идентификатор. По окончании идентификатора, либо по сигналу о смене режима, либо по сигналу о завершении работы выделенный идентификатор помещается в справочник идентификаторов (отдельный для каждого блока).


сигналы не осилил, зато сжульничал вставив пробел в конец программ :)

dimka писал(а): В режимах первой и второй программ идентификаторы, не опознанные как ключевые слова, удаляются из буфера, но в соответствующем списке для каждой программы сохраняется, в каком порядке эти идентификаторы следовали. Ключевые слова остаются в буфере. После обработки очередного идентификатора буфер выталкивается далее в конвейер. Символы, не относящиеся к идентификаторам, подаются на выход, минуя буфер. На выход подаётся очередной символ, текущий блок обработки и списки позиций идентификаторов программ. Далее по конвейеру транслируются сигналы смены режима (текущего блока) и завершения работы.


сделал немного проще и опять сжульничал, по условиям \ символа в тексте после прохожнения фильтнов не было, так что опознаные слова и идентификаторы заменял на \порядковый_номер, неопознаные на \последний_номер+1 и вносил после этого в конец списка единого для ключевых слов и идентификаторов, что наверное несколько расходится с теорией компиляции - зато работает быстрее - и не нада сверять потом буфера программ с отдельными списками :)
при переходе к следующей проге опять таки ничего не удалял, о просто конец списка устанавливал на последнее ключевое слово

dimka писал(а):5) На четвёртом шаге действует компаратор программ. Он накапливает в буфере неопределённого размера текст первой программы, а при начале получения текста второй программы производит посимвольное сравнение второй программы с текстом первой, выталкивая из буфера первой программы символы по схеме FIFO. Поскольку тексты очищены от специфических идентификаторов, строк и комментариев, сжаты по пробелам, они по условию задачи должны быть идентичны. Если встретится различие в символах, в выходной поток записывается NO, и работа программы завершается. Если поступил сигнал о завершении работы, а различий не встретилось, считается, что тексты программ идентичны и начинается сравнение идентификаторов. Списки идентификаторов проходятся параллельно, и каждый элемент одного списка сохраняется вместе с соответствующим элементом другого списка в справочник соответствия при условии, что элемента первого списка в справочнике ещё нет; если же элемент в справочнике находится, то проверяется, равна ли пара этого элемента справочника элементу из второго списка. Если равенство отсуствует, в выходной поток записывается NO, и работа программы завершается. Если списки порядка идентификаторов программ закончились, в выходной поток записывается YES, и работа программы завершается.


а вот тут немного не понял, помоему
printf ... for +++
и
... print for +++
окажутся идентичными
так что каждый элемент списка должен еще и содержать место ид и ключевого слова в тексте
но тк я сжульничал на 4 этапе, то на 5 требуется только сравнить тексты программ :)
но в принципе эффективность 4+5 этапов по моим расчетам одинакова

dimka писал(а):Конвейер в принципе можно реализовать и на едином буфере, но, по-моему, это менее удобно для фильтров.


не то слово, вроде огромные трудности в реализации

dimka писал(а): В качестве справочников можно использовать любую структуру данных, ориентированную на быстрый поиск, например, сбалансированное бинарное дерево. Можно исключить справочники идентификаторов для программ, сохраняя в списках сами идентификаторы - ограничение по памяти и ограничения входного файла позволяют это сделать, но процесс сравнения строк более долгий, чем сравнение указателей на элемент справочника. Поскольку сбалансированные бинарные деревья в библиотеках языка Pascal не всегда встречаются, а написание их достаточно трудоёмко, можно их заменить на более простые в программировании структуры, но это будет стоить уменьшения производительности программы.


во во второе по вероятности узкое место программы, после фильтров конечно

ну вроде нашел ответы :)

ev: проверяем что после хитрых плагинов получается, а то читать сложно
Mayor
новенький
 
Сообщения: 20
Зарегистрирован: 04.09.2007 16:55:14


Вернуться в Free Pascal Compiler

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 7

Рейтинг@Mail.ru