C (465 знаков)
#define F for(i=0;P-8;i+=2)
#define V t[i
#define P V+1]
#define S V+2]),K(&L,4),i-=2)
#define L V-2]
K(double*t,int i){for(*++t=4;*t-8;*++t=V])*++t=V];}M(double*t){int i,p,b;
F if(!P)for(p=1,b=i;i+=2,p;)P?P-1||--p||(P=8,M(t+b+2),K(t+b,i-b),i=b):++p;
F P-6||(L=pow(L,S;F P-2&&P-7||(L*=(P-7?V+2]:1/S;F P-4&&(L+=(P-5?V+2]:-S;
F L=V];}E(char*s,char*r){double t[99];char*e,i=2,z=0;for(;*s;i+=2)V]=
strtod(s,&e),P=z=e-s&&z-4&&z-1?s=e,4:*s++&7;P=8;M(t+2);sprintf(r,"%g",*t);}
Первые пять символов новой строки обязательны, остальные нужны только для удобства чтения. Я посчитал первые пять символов новой строки по одному символу. Если вы хотите измерить его в строках, это было 28 строк, прежде чем я удалил все пробелы, но это довольно бессмысленное число. Это могло быть что угодно, от 6 строк до миллиона, в зависимости от того, как я его отформатировал.
Точка входа - E()
(для оценки). Первый параметр - это входная строка, а второй параметр указывает на выходную строку и должен быть выделен вызывающей стороной (в соответствии с обычными стандартами C). Он может обрабатывать до 47 токенов, где токен является либо оператором (одним из +-*/^()
), либо числом с плавающей запятой. Операторы унарного знака не считаются отдельным токеном.
Этот код в общих чертах основан на проекте, который я реализовал много лет назад в качестве упражнения. Я убрал всю обработку ошибок и пропуск пробелов и переделал ее, используя технику гольфа. Ниже приведены 28 строк с достаточным форматированием, чтобы я смог его написать, но, вероятно, недостаточно, чтобы прочитать его. Вы захотите #include
<stdlib.h>
, <stdio.h>
и <math.h>
(или см. Примечание внизу).
См. После кода объяснение того, как это работает.
#define F for(i=0;P-8;i+=2)
#define V t[i
#define P V+1]
#define S V+2]),K(&L,4),i-=2)
#define L V-2]
K(double*t,int i){
for(*++t=4;*t-8;*++t=V])
*++t=V];
}
M(double*t){
int i,p,b;
F if(!P)
for(p=1,b=i;i+=2,p;)
P?P-1||--p||(P=8,M(t+b+2),K(t+b,i-b),i=b):++p;
F P-6||(L=pow(L,S;
F P-2&&P-7||(L*=(P-7?V+2]:1/S;
F P-4&&(L+=(P-5?V+2]:-S;
F L=V];
}
E(char*s,char*r){
double t[99];
char*e,i=2,z=0;
for(;*s;i+=2)
V]=strtod(s,&e),P=z=e-s&&z-4&&z-1?s=e,4:*s++&7;
P=8;
M(t+2);
sprintf(r,"%g",*t);
}
Первый шаг - токенизация. Массив чисел типа double содержит два значения для каждого токена, оператор (P
, потому что O
слишком похож на ноль) и значение (V
). Эта разметка выполняется в цикле for
в E()
. Он также имеет дело с любыми унарными операторами +
и -
, включая их в константу.
Поле оператора массива токенов может иметь одно из следующих значений:
0: (
1: )
2: *
3 : +
4: постоянное значение с плавающей запятой
5: -
6 : ^
7: /
8: конец строки токена
Эта схема в значительной степени была получена Дэниелом Мартином, который заметил, что последний 3 бита были уникальными в представлении ASCII каждого из операторов в этой задаче.
Несжатая версия E()
будет выглядеть примерно так:
void Evaluate(char *expression, char *result){
double tokenList[99];
char *parseEnd;
int i = 2, prevOperator = 0;
/* i must start at 2, because the EvalTokens will write before the
* beginning of the array. This is to allow overwriting an opening
* parenthesis with the value of the subexpression. */
for(; *expression != 0; i += 2){
/* try to parse a constant floating-point value */
tokenList[i] = strtod(expression, &parseEnd);
/* explanation below code */
if(parseEnd != expression && prevOperator != 4/*constant*/ &&
prevOperator != 1/*close paren*/){
expression = parseEnd;
prevOperator = tokenList[i + 1] = 4/*constant*/;
}else{
/* it's an operator */
prevOperator = tokenList[i + 1] = *expression & 7;
expression++;
}
}
/* done parsing, add end-of-token-string operator */
tokenList[i + 1] = 8/*end*/
/* Evaluate the expression in the token list */
EvalTokens(tokenList + 2); /* remember the offset by 2 above? */
sprintf(result, "%g", tokenList[0]/* result ends up in first value */);
}
Поскольку нам гарантирован действительный ввод, единственная причина, по которой синтаксический анализ не удастся, будет заключаться в том, что следующий токен является оператором. В этом случае указатель parseEnd
будет таким же, как tokenStart
. Мы также должны обработать случай, когда синтаксический анализ успешно, но на самом деле нам нужен был оператор. Это может произойти для операторов сложения и вычитания, если непосредственно не следует знаковый оператор. Другими словами, учитывая выражение 4-6
, мы хотим проанализировать его как {4, -, 6}
, а не как {4, -6}
. С другой стороны, учитывая 4+-6
, мы должны проанализировать его как {4, +, -6}
. Решение довольно простое. Если синтаксический анализ завершился неудачно ИЛИ предыдущий токен был константой или закрывающей круглой скобкой (фактически, подвыражение, которое будет оцениваться как константа), то текущий токен является оператором, в противном случае - константой.
После того, как токенизация завершена, вычисление и сворачивание выполняются с помощью вызова M()
, который сначала ищет любые совпадающие пары круглых скобок и обрабатывает содержащиеся внутри подвыражения, рекурсивно вызывая себя. Затем он обрабатывает операторы, сначала возведение в степень, затем умножение и деление вместе и, наконец, сложение и вычитание вместе. Поскольку ожидается правильно сформированный ввод (как указано в задаче), он не проверяет явно оператор сложения, поскольку это последний допустимый оператор после обработки всех остальных.
Расчетная функция без сжатия гольфа выглядела бы примерно так:
void EvalTokens(double *tokenList){
int i, parenLevel, parenStart;
for(i = 0; tokenList[i + 1] != 8/*end*/; i+= 2)
if(tokenList[i + 1] == 0/*open paren*/)
for(parenLevel = 1, parenStart = i; i += 2, parenLevel > 0){
if(tokenList[i + 1] == 0/*another open paren*/)
parenLevel++;
else if(tokenList[i + 1] == 1/*close paren*/)
if(--parenLevel == 0){
/* make this a temporary end of list */
tokenList[i + 1] = 8;
/* recursively handle the subexpression */
EvalTokens(tokenList + parenStart + 2);
/* fold the subexpression out */
FoldTokens(tokenList + parenStart, i - parenStart);
/* bring i back to where the folded value of the
* subexpression is now */
i = parenStart;
}
}
for(i = 0; tokenList[i + 1] != 8/*end*/; i+= 2)
if(tokenList[i + 1] == 6/*exponentiation operator (^)*/){
tokenList[i - 2] = pow(tokenList[i - 2], tokenList[i + 2]);
FoldTokens(tokenList + i - 2, 4);
i -= 2;
}
for(i = 0; tokenList[i + 1] != 8/*end*/; i+= 2)
if(tokenList[i + 1] == 2/*multiplication operator (*)*/ ||
tokenList[i + 1] == 7/*division operator (/)*/){
tokenList[i - 2] *=
(tokenList[i + 1] == 2 ?
tokenList[i + 2] :
1 / tokenList[i + 2]);
FoldTokens(tokenList + i - 2, 4);
i -= 2;
}
for(i = 0; tokenList[i + 1] != 8/*end*/; i+= 2)
if(tokenList[i + 1] != 4/*constant*/){
tokenList[i - 2] +=
(tokenList[i + 1] == 3 ?
tokenList[i + 2] :
-tokenList[i + 2]);
FoldTokens(tokenList + i - 2, 4);
i -= 2;
}
tokenList[-2] = tokenList[0];
/* the compressed code does the above in a loop, equivalent to:
*
* for(i = 0; tokenList[i + 1] != 8; i+= 2)
* tokenList[i - 2] = tokenList[i];
*
* This loop will actually only iterate once, and thanks to the
* liberal use of macros, is shorter. */
}
Некоторое сжатие, вероятно, сделало бы эту функцию более легкой для чтения.
После выполнения операции операнды и оператор сворачиваются из списка токенов с помощью K()
(вызываемого с помощью макроса S). Результат операции остается константой вместо свернутого выражения. Следовательно, окончательный результат остается в начале массива токенов, поэтому, когда элемент управления возвращается к E()
, он просто выводит его в строку, пользуясь тем фактом, что первое значение в массиве равно поле значения токена.
Этот вызов FoldTokens()
происходит либо после выполнения операции (^
, *
, /
, +
или -
), или после обработки части выражения (заключенной в круглые скобки). Подпрограмма FoldTokens()
гарантирует, что значение результата имеет правильный тип оператора (4), а затем копирует остальную часть большего выражения подвыражения. Например, когда выражение 2+6*4+1
обрабатывается, EvalTokens()
сначала вычисляет 6*4
, оставляя результат вместо 6
(2+24*4+1
). FoldTokens()
затем удаляет остальную часть подвыражения 24*4
, оставляя 2+24+1
.
void FoldTokens(double *tokenList, int offset){
tokenList++;
tokenList[0] = 4; // force value to constant
while(tokenList[0] != 8/*end of token string*/){
tokenList[0] = tokenList[offset];
tokenList[1] = tokenList[offset + 1];
tokenList += 2;
}
}
Вот и все. Макросы предназначены только для замены обычных операций, а все остальное - это просто сжатие вышеуказанного.
strager настаивает, чтобы код содержал #include
инструкции, так как он не будет работать правильно без надлежащего прямого удаления strtod
и pow
и функций. Поскольку задача требует только функции, а не полной программы, я считаю, что этого не требуется. Однако форвардные объявления можно добавить с минимальными затратами, добавив следующий код:
#define D double
D strtod(),pow();
Затем я бы заменил все экземпляры double
в коде на D
. Это добавило бы к коду 19 символов, в результате чего общее количество увеличилось бы до 484. С другой стороны, я мог бы также преобразовать свою функцию так, чтобы она возвращала двойное значение вместо строки, как он, который обрезал бы 15 символов, изменив E()
к этому:
D E(char*s){
D t[99];
char*e,i=2,z=0;
for(;*s;i+=2)
V]=strtod(s,&e),P=z=e-s&&z-4&&z-1?s=e,4:*s++&7;
P=8;
M(t+2);
return*t;
}
Это сделало бы общий размер кода 469 символов (или 452 без предварительных объявлений strtod
и pow
, но с макросом D
). Можно даже обрезать еще 1 символ, потребовав от вызывающего передать указатель на двойное значение для возвращаемого значения:
E(char*s,D*r){
D t[99];
char*e,i=2,z=0;
for(;*s;i+=2)
V=strtod(s,&e),P=z=e-s&&z-4&&z-1?s=e,4:*s++&7;
P=8;
M(t+2);
*r=*t;
}
Я оставляю читателю решать, какая версия подходит.
person
Community
schedule
07.09.2009
eval()
, но что насчет этого вопроса, запрещающего использование регулярных выражений? - person Chris Lutz   schedule 06.09.2009s/(\d+)\s*\+\s*(\d+)/$1 + $2/eg
- person Matthew Scharley   schedule 06.09.2009parseInt
Javascript илиread
Haskell считаться библиотекой синтаксического анализатора? - person sth   schedule 06.09.2009log()
, а также в PHP, как набор регулярных выражений с параметром/e
. - person Matthew Scharley   schedule 06.09.2009-256
? - person Alix Axel   schedule 05.05.2012