Copyright (C) IOI日本委員会 1995. All rights reserved.


問題6.

 数式とは、「項」と呼ばれるものであるか、または、「数式の後ろに +記号を書き、さらにその後ろに項をつづけて書いたもの」であるか、または、 「数式の後ろに−記号を書き、さらにその後ろに項をつづけて書いたもの」 のことである。数式とは何かをこのように定義することを

  <数式>::=<項>|<数式>+<項>|<数式>−<項>

という式で表わす。 < ... > は、「... というもの」 を示している。記号 ::= の左辺は定義したいものであり、それは右辺 のような形をしている文字列として定義される。右辺に現れる記号 | は 「または」を表わす。
 同様に、項とは何か、因子とは何か、などを以下のように定義する。

  <項>::=<因子>|<項>*<因子>|<項>/<因子>
  <因子>::=<素子>|<素子>^<因子>
  <素子>::=<定数>|−<素子>|(<数式>)
  <定数>::=<数字>|<数字><定数>
  <数字>::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

ここで、+, -, *, / は四則演算子であり、記号 ^ はべき乗 (x^y は xのy乗)を表わす。ただし、 x=0 で y≦0 の場合と x<0 で y が整数でない場合には x^y は定義されないものとする。

 例えば -7*(65-4^3)/2 が数式であることは、図(省略)のように分かる。 これを数式 -7*(65-4^3)/2 の構文木という。 この構文木は、演算子の結合順序が
   [ [-7] * (65 - [4 ^ 3]) ] 2
であることを示しており、従って、数式 -7*(65-4^3)/2 の値は -3.5 である。
 数式とは何かを上記のように定義しておくと、どの数式の構文木も 一意的に定まる。

(1) 次の数式の構文木を考え、それに基づき、この数式の値を計算 せよ。 [1点]
   223---2^2^3

(2)下記の3つの異なる言語で書かれたプログラムは、数式を文字列 (ただし、空白を含まないとする)として入力し、その値を計算する プログラムである。ただし、文字コードはASCIIとする。 いずれか一つの言語について、空欄を埋めよ。空欄には、関数や手続き (プロシ−ジャ)の呼び出し、あるいは代入文が入る。Pascalでは、 空欄(a),(b)の出現順が逆になっているので注意すること。
 参考までに記すと、数式、項、因子、素子、定数は、英語ではそれぞれ expression, term, factor, primary, constant という。 [2点]


BASICプログラム(6)

DECLARE FUNCTION expression (expr$, value)
DECLARE FUNCTION term (expr$, value)
DECLARE FUNCTION factor (expr$, value)
DECLARE FUNCTION primary (expr$, value)
DECLARE FUNCTION constant (expr$, value)
DECLARE FUNCTION pow (x, y)
CONST true = -1, false = 0

   CLS
   PRINT "数式を入力しなさい。"
   INPUT expr$
   IF expression(expr$, value) THEN
      PRINT expr$; "の値は";
      PRINT USING "#.######"; value;
      PRINT "です。"
   ELSE PRINT "数式の構文に合致しませんでした。"
   END IF
END

FUNCTION expression (expr$, value)
   IF term(expr$, value) THEN expression = true: EXIT FUNCTION
   length = LEN(expr$)
   IF length < 3 THEN expression = false: EXIT FUNCTION
   FOR i = 2 TO length - 1
      op$ = MID$(expr$, i, 1)
      IF op$ = "+" OR op$ = "-" THEN
         isExpr = expression(LEFT$(expr$, i - 1), value1)
         isTerm = term(RIGHT$(expr$, length - i), value2)
         IF isExpr AND isTerm THEN
            SELECT CASE op$
               CASE "+": value = value1 + value2
               CASE "-": value = value1 - value2
            END SELECT
            expression = true: EXIT FUNCTION
         END IF
      END IF
   NEXT i
   expression = false
END FUNCTION

FUNCTION term (expr$, value)
   IF factor(expr$, value) THEN term = true: EXIT FUNCTION
   length = LEN(expr$)
   IF length < 3 THEN term = false: EXIT FUNCTION
   FOR i = 2 TO length - 1
      op$ = MID$(expr$, i, 1)
      IF op$ = "*" OR op$ = "/" THEN
         isTerm = term(LEFT$(expr$, i - 1), value1)
         isFactor = factor(RIGHT$(expr$, length - i), value2)
         IF isTerm AND isFactor THEN
            SELECT CASE op$
               CASE "*": value = value1 * value2
               CASE "/": value = value1 / value2
            END SELECT
            term = true: EXIT FUNCTION
         END IF
      END IF
   NEXT i
   term = false
END FUNCTION

FUNCTION factor (expr$, value)
   IF primary(expr$, value) THEN factor = true: EXIT FUNCTION
   length = LEN(expr$)
   IF length < 3 THEN factor = false: EXIT FUNCTION
   FOR i = 2 TO length - 1
      IF MID$(expr$, i, 1) = "^" THEN
         isPrimary = primary(LEFT$(expr$, i - 1), value1)
         isFactor = factor(RIGHT$(expr$, length - i), value2)
         IF isPrimary AND isFactor THEN
            factor = true: value = pow(value1, value2): EXIT FUNCTION
         ELSE factor = false: EXIT FUNCTION
         END IF
      END IF
   NEXT i
   factor = false
END FUNCTION

FUNCTION pow (x, y)  'xのy乗を計算する関数
   IF x > 0 THEN
      pow = EXP(y * LOG(x))
   ELSEIF x = 0 THEN
      IF y > 0 THEN pow = 0 ELSE PRINT "定義域エラ−!": STOP
   ELSE 'x < 0
      IF INT(y) <> y THEN PRINT "定義域エラ−!": STOP
      absy = ABS(y): p = 1
      WHILE absy > 0
        p = p * x: absy = absy - 1
      WEND
      IF y > 0 THEN pow = p ELSE pow = 1 / p
   END IF
END FUNCTION

FUNCTION primary (expr$, value)
   IF constant(expr$, value) THEN primary = true: EXIT FUNCTION
   IF MID$(expr$, 1, 1) = "-" THEN
      IF primary(MID$(expr$, 2), fvalue) THEN
         primary = true: value = -fvalue: EXIT FUNCTION
      ELSE primary = false: EXIT FUNCTION
      END IF
   END IF
   IF LEFT$(expr$, 1) = "(" AND RIGHT$(expr$, 1) = ")" THEN
         ------------------------------------------------ 
     IF |                    (a)                         | THEN
         ------------------------------------------------ 
         primary = true: EXIT FUNCTION
      ELSE primary = false: EXIT FUNCTION
      END IF
   END IF
END FUNCTION

FUNCTION constant (expr$, value)
   length = LEN(expr$)
   FOR i = 1 TO length
     IF MID$(expr$, i, 1) < "0" OR "9" < MID$(expr$, i, 1) THEN EXIT FOR
   NEXT i
   IF i <= length THEN
      constant = false
   ELSE
      constant = true
       -----------------
      |       (b)       |
       -----------------
   END IF
END FUNCTION


Cプログラム(6)

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <math.h>

#define MaxLen 255  /* 数式の最大長 */
#define TRUE 1
#define FALSE 0

typedef char *STRING;

int expression(STRING, float *);
int term(STRING, float *);
int factor(STRING, float *);
int primary(STRING, float *);
int constant(STRING, int *);

void main()
{
   char expr[MaxLen];	
   float value;

   printf("数式を入力しなさい。\n");
   gets(expr);
   if (expression(expr,&value))
      printf("%sの値は%fです。\n", expr,value);
    else printf("数式の構文に合致しませんでした。\n");
}

int expression(STRING expr, float *value)
{
   char op; int length,i; float value1,value2;

   if (term(expr, value)) return TRUE;
   if ((length = strlen(expr)) < 3) return FALSE;
   for (i=1; i<length-1; i++) {
      if (expr[i]!='+' && expr[i]!='-') continue;
      op = expr[i]; expr[i] = '\0';
      if (expression(expr,&value1) && term(expr+i+1,&value2)) { 
         switch (op) {
            case '+': *value = value1+value2; break;
            case '-': *value = value1-value2; break;
         }
         expr[i] = op;
         return TRUE;
      }
      expr[i] = op; 
   }
   return FALSE;
}

int term(STRING expr, float *value)
{
   char op; int length,i; float value1,value2;

   if (factor(expr, value)) return TRUE;
   if ((length = strlen(expr)) < 3) return FALSE;
   for (i=1; i<length-1; i++) {
      if (expr[i]!='*' && expr[i]!='/') continue;
      op = expr[i]; expr[i] = '\0';
      if (term(expr,&value1) && factor(expr+i+1,&value2)) { 
         switch (op) {
            case '*': *value = value1*value2; break;
            case '/': *value = value1/value2; break;
         }
         expr[i] = op;
         return TRUE;
      }
      expr[i] = op; 
   }
   return FALSE;
}
                            
int factor(STRING expr, float *value)
{
   int length,i; float value1,value2;

   if (primary(expr, value)) return TRUE;
   if ((length = strlen(expr)) < 3) return FALSE;
   for (i=1; i<length-1; i++) {
      if (expr[i]!='^') continue;
      expr[i] = '\0';
      if (primary(expr,&value1) && factor(expr+i+1,&value2)) { 
         *value = pow(value1,value2);
         expr[i] = '^';
         return TRUE;
      }
      expr[i] = '^'; 
   }
   return FALSE;
}

int primary(STRING expr, float *value)
{
   int ivalue,last; float fvalue;

   if (constant(expr, &ivalue)) {
      *value = (float)ivalue; return TRUE;
   }
   if (expr[0]=='-' && primary(expr+1, &fvalue)) {
      *value = -fvalue; return TRUE;
   }
   if (expr[0]=='(' && expr[last=strlen(expr)-1]==')') {
      expr[last] = '\0';
            ----------------------
      if ( |         (a)          | ) {
            ----------------------
         expr[last] = ')'; return TRUE;
      }
      expr[last] = ')'; return FALSE;
   }
   return FALSE;
}

int constant(STRING expr, int *value)
{
   int i;

   for (i=0; i<strlen(expr); i++)
      if (!isdigit(expr[i])) return FALSE;
    -------------------
   |       (b)         |
    -------------------
   return TRUE;
}


Pascalプログラム(6)

program problem7;
   var   expr: string;	
         value: real;
         n: longint;

   function constant(var expr: string; var value: longint): Boolean;
      var i,error: integer;
   begin
      for i:=1 to length(expr) do
         if (expr[i]<'0') or ('9'<expr[i]) then begin
            constant := false; exit;
         end;
       ---------------------
      |        (b)          |
       ---------------------
      if error<>0 then begin writeln('変換エラ−!'); halt end;
      constant := true;
   end;

   function expression(expr: string; var value: real): Boolean; forward;

   function primary(expr: string; var value: real): Boolean;
      var len: integer; ivalue: longint; fvalue: real;
   begin
      primary := true;
      if constant(expr, ivalue) then begin
         value := ivalue; exit;
      end;
      len := length(expr);
      if (expr[1]='-') and primary(copy(expr,2,len-1),fvalue) then begin
         value := -fvalue; exit;
      end;
      if (expr[1]='(') and (expr[len]=')')
              -------------------------------
         and |              (a)              | then exit;
              -------------------------------
      primary := false;
   end;

   function factor(expr: string; var value: real): Boolean;
      var len,i: integer; value1,value2: real;
     
      function pow(x,y: real): real;  { xのy乗を計算する関数 }
         var p: real; iy,absy: integer;
      begin
         if x>0 then pow := exp(y*ln(x)) else
         if x=0 then
            if y>0 then pow := 0
            else begin writeln('定義域エラ−!'); halt end
         else { x<0 } begin
            iy := trunc(y); 
            if iy<>y then begin writeln('定義域エラ−!'); halt end;
            absy := abs(iy); p := 1.0;
            while absy>0 do begin p := p*x; dec(absy) end;
            if y>0 then pow := p else pow := 1/p;
         end;
      end;      
   
   begin
      factor := true;
      if primary(expr, value) then exit;
      len := length(expr);
      if len<3 then begin factor := false; exit end;
      for i:=2 to len-1 do 
         if (expr[i]='^') and primary(copy(expr,1,i-1),value1)
                          and factor(copy(expr,i+1,len-i),value2)
         then begin value := pow(value1,value2); exit end;
      factor := false;
   end;

   function term(expr: string; var value: real): Boolean;
      var len,i: integer; value1,value2: real;
   begin
      term := true;
      if factor(expr, value) then exit;
      len := length(expr);
      if len<3 then begin term := false; exit end;
      for i:=2 to len-1 do 
         if ((expr[i]='*') or (expr[i]='/'))
            and term(copy(expr,1,i-1),value1)
            and factor(copy(expr,i+1,len-i),value2)
         then begin  
            case expr[i] of
               '*' : value := value1*value2;
               '/' : value := value1/value2;
            end;
            exit;
         end;
      term := false;
   end;
     
   function expression(expr: string; var value: real): Boolean;
      var len,i: integer; value1,value2: real;
   begin
      expression := true;
      if term(expr,value) then exit;
      len := length(expr);
      if len<3 then begin expression := false; exit end;
      for i:=2 to len-1 do 
         if ((expr[i]='+') or (expr[i]='-'))
            and expression(copy(expr,1,i-1),value1)
            and term(copy(expr,i+1,len-i),value2)
         then begin  
            case expr[i] of
               '+' : value := value1+value2;
               '-' : value := value1-value2;
            end;
            exit;
         end;
      expression := false;
   end;


begin
   writeln('数式を入力しなさい。');
   readln(expr);
   if expression(expr,value) then
      writeln(expr, 'の値は', value:0:6, 'です。')
   else writeln('数式の構文に合致しませんでした。');
end.


JOI'95へ戻る

JOIホームページへ戻る