文字列から計算させるには?


ぜいにくん  2003-06-03 06:29:12  No: 3685

具体的に質問します。
Editコンポーネントと
Buttonコンポーネント
Labelコンポーネントを一つずつ配置します。

ユーザー側がEditに計算式をキーボ−ドで入力します。
例えば

7+5*3-8

のように。

で、Buttonを押すことでLabelにその結果を表示させるようにしたいのです。

この場合なら

14

のように。

つまり、Editに入力された文字列の中で、
数字と四則演算子の+-*/を区別させて、配列にそれらの要素を格納させて、なおかつどこかで計算させようというのが
私の考えなのですが、
Pos関数や、AnsiPos関数のような、文字列に関係する関数を使ってうまくできないものかと思って考えていますが、なかなかうまく出来ません。
何か良いアイディアあればお願いします。


たかみちえ  URL  2003-06-03 07:31:52  No: 3686

確か同じような質問が、以前あったと思います。
検索で探してみてください。

  ところで、自力でやるとしても、
Pos関数などでは効率が悪いような気がします。

Delphiの文字列型は、配列のように使う(Str[1]など)こともできるので、
Deleteで、解釈が終わるたびに消しつつしながら、
変数をためていくのがいいとおもいますよ。
(実際には括弧とかもあるし、Deleteではむりっぽそうですけど。他いくつかの操作を組み合わせる必要がありそうです)


ぜいにくん  2003-06-03 07:47:35  No: 3687

>Delphiの文字列型は、配列のように使う(Str[1]など)こともできるので、
そんなことできるのですか?
まだDelphi使い始めてまもないので…。
それがあるのなら私の悩みも解決できるかも…。
ちょっと調べてみます。


にしの  2003-06-03 08:56:44  No: 3688

逆ポーランド記法なら簡単なんですけどね。
# 7+5*3-8であれば、7 5 3 * + 8 -となる。

四則演算は、次のようなBNFで記述できます。

expr: expr '+' expr
    | expr '-' expr
    | expr '*' expr
    | expr '/' expr
    | '-' expr
    | '(' expr ')'
    | NUMBER
    ;
ただし、'+','-'よりも、'*','/'のほうが優先度が高く、さらに単項負号である'-'(「マイナス1」などの記号。減算の'-'ではない)は一番優先度が高い。

ちなみに、NUMBERに当たる正規表現は、
-?(([0-9]+)|([0-9]*\.[0-9]+)([eE][-+]?[0-9]+)?)
です。
これを自前で処理させるとしたら至難の業です。

もし、このような字句解析・構文解析に興味がおありなら、Lex/Yaccをおすすめします。

もともとC言語用ですが、
http://www2.big.or.jp/~osamu/Delphi/
こちらに、TPLex, TPYacc(TurboPascal用Lex/Yacc)のDelphi用コードがあります。
サンプルに、計算するものもあります。


ぜいにくん  2003-06-03 10:34:17  No: 3689

う…うーん。なんか高度な話なようで…。
逆ポーランド記法とかBNFとか…。

やっぱり難しいんですかねぇ…。
すいません。もう少し考えてみます。


たかみちえ  URL  2003-06-03 15:59:33  No: 3690

>> Delphiの文字列型は、配列のように使う(Str[1]など)こともできるので、
> そんなことできるのですか?
  Anothor HTML Lintを実行し、その出力文字列の最後の行から、
点数を抜き出します。(おとといつくったばっか)
  e,sはInteger。ResultLabel.Captionには最終行の文字列
PlaySoundはうちにあるTPlaySoundコンポーネント。

  // 点を検索、点数を取得
  e := Pos('点',ResultLabel.Caption); // 終了位置を取得
  Dec(e);
  s := e;
  while ResultLabel.Caption[s] in ['0'..'9','-'] do
    Dec(s); // 数字ではなくなるまで、開始位置をデクリメント
  case StrToInt(Copy(ResultLabel.Caption,s,e - s + 1)) of
    100   : PlaySound.Play('OnPerfect'); // 100点
    70..99: PlaySound.Play('OnFully');   // 70点以上
    30..69: PlaySound.Play('OnNormal');  // 30点以上
    0..29 : PlaySound.Play('OnWrong');   // 30点未満
  else      PlaySound.Play('OnFailure'); // マイナス点
  end;

  ほんとはeのときにエラー処理をちゃんとしておきましょう^^;
(定義ファイルが見つからなかったりしたら、採点できないので)


Halbow  URL  2003-06-03 20:50:24  No: 3691

こんにちは、Halbow です。

> つまり、Editに入力された文字列の中で、
> 数字と四則演算子の+-*/を区別させて、配列にそれらの要素を格納させて、
> なおかつどこかで計算させようというのが

実行時に入力された数式を解釈して計算するコンポーネントがあります。
Delphian World  

http://home1.infonia.ne.jp/~delphian/delphi/

の  Miscellanious  のところの

数式解析コンポーネント MathsParser  (藤巻氏作)

を使えばできると思います。


きんにくん  2003-06-05 21:18:44  No: 3692

遅くなりました。すいません。
皆さん有難うございます。
特に、Halbowさんのおっしゃってくれた数式解析コンポーネント MathsParserは使えそうです。フリーというのも嬉しかったですし。
まだ、このコンポーネント自体の能力や、使い方はイマイチ把握していませんが、
ちょっと調べてみたいと思います。

うまくいきましたら「解決」として報告します。
有難うございます。


ぜいにくん  2003-06-06 08:46:04  No: 3693

すいません。名前間違えました。
きんにくんではなく、ぜいにくんです。


  2020-11-21 10:23:59  No: 149387

bat や シェルに丸投げしちゃって、テキスト書き出しする。
そのテキストから読み込む・・・という方法もいいかと思いますよ。


AAA  2020-11-22 01:01:15  No: 149388

+ -  */ 四則演算  ¥ 除算  ^ 乗算 , ()あり , */¥^ 優先計算 

小数対応なので Extended で値が返ってきます

//Space飛ばす(前方)
procedure SkipSpace(TEXT: String; var Index: Integer);
var
    B: Boolean;
    TEXT_LENGTH: Integer;
begin
    TEXT_LENGTH := Length(TEXT);
    B := True;
    while  (INDEX <= TEXT_LENGTH) and (B = True) do
    begin
      if (TEXT[Index] = ' ') then Inc(INDEX) else B := False;
    end;
end;

//Space飛ばす(後方)
procedure SkipSpace2(TEXT: String; var Index: Integer);
var
    B: Boolean;
    TEXT_LENGTH: Integer;
begin
    TEXT_LENGTH := Length(TEXT);
    B := True;
    while  (INDEX <= TEXT_LENGTH) and (B = True) do
    begin
      if (TEXT[Index] = ' ') then Dec(INDEX) else B := False;
    end;
end;

function GetValue(TEXT: String; var INDEX: Integer; var SELECT_TEXT: String): Boolean;
var
    B: Boolean;
    SAVE_INDEX: Integer;
    TEXT_LENGTH: Integer;
begin
    TEXT_LENGTH := Length(TEXT);
    if (INDEX <= TEXT_LENGTH)  then
    begin
      SAVE_INDEX := Index;
      if (TEXT[INDEX] = '-') or (TEXT[INDEX] = '+') then Inc(INDEX);
      if TEXT[INDEX] = '$' then Inc(INDEX);
      B := True;
      while  (INDEX <= TEXT_LENGTH) and (B = True) do
      begin
        if CharInSet(TEXT[INDEX],[' ','+','-','*','/','\','^']) = True then
        begin
          B := False;
        end
        else
        begin
          Inc(INDEX);
        end;
      end;
      SELECT_TEXT := Copy(TEXT,SAVE_INDEX,INDEX-SAVE_INDEX);
      RESULT  := True;
    end
    else
    begin
      RESULT := False;
    end;
end;

function GetValue2(TEXT: String; var INDEX: Integer; var SELECT_TEXT: String): Boolean;
var
    B: Boolean;
    SAVE_INDEX: Integer;
    INDEX2: Integer;
begin
    if (0 <= INDEX )  then
    begin
      SAVE_INDEX := Index;
      B := True;
      while  (0 < INDEX) and (B = True) do
      begin
        if TEXT[INDEX] = ' ' then
        begin
          B := False;
        end
        else
        if CharInSet(TEXT[INDEX],['+','-']) = True then
        begin
          INDEX2 := INDEX - 1;
          SkipSpace2(TEXT,INDEX2);

          if CharInSet(TEXT[INDEX2],['+','-','*','/','\','^']) = True then
          begin
            INDEX := INDEX2 + 1;
          end;
          B := False;
        end
        else
        if CharInSet(TEXT[INDEX],['*','/','\','^']) = True then
        begin
          B := False;
        end
        else
        begin
          Dec(INDEX);
        end;
      end;
      SELECT_TEXT := Copy(TEXT,INDEX+1,SAVE_INDEX-INDEX);
      RESULT  := True;

      Inc(INDEX);
    end
    else
    begin
      RESULT := False;
    end;
end;

function SelTextToFloat(SELECT_TEXT: String; var Value: Extended): Boolean;
begin
    RESULT := True;
    try
      if Pos('.',SELECT_TEXT) = 0 then
      begin
        VALUE := Extended(StrToInt(SELECT_TEXT));
      end
      else
      begin
        VALUE := StrToFloat(SELECT_TEXT);
      end;
    except
     //数値エラー
      RESULT := False;
    end;
end;

function LastPos(SubStr,TEXT: String): Integer;
var
    Offset: Integer;
begin
    RESULT := 0;

    Offset := 1;
    while Offset <> 0 do
    begin
      Offset := Pos(SubStr,TEXT,Offset);
      if Offset <> 0 then
      begin
        RESULT := Offset;
        Inc(Offset);
      end;
    end;
end;

function Replace(TEXT: String; I1,I2: Integer; NewText: String): String;
var
    S1,S2: String;
begin
    S1 := Copy(TEXT,1,I1-1);
    S2 := Copy(TEXT,I2);
    RESULT := S1 + NewText + S2;
end;

//べき乗
function Power(X: Extended; Y: Integer): Extended;
var
    I: Integer;
begin
    RESULT := 0;
    for I:=1 to Y do
    begin
      RESULT := RESULT + X;
    end;
end;

procedure XXX(Index: Integer; TEXT: String; Operation: Char; var ARESULT: Extended; var ERROR: Integer);
var
    TEXT_LENGTH: Integer;
    SELECT_TEXT: String;
    C: Char;
    VALUE: Extended;
begin
    if ERROR = 0 then
    begin
      TEXT_LENGTH := Length(TEXT);

      SkipSpace(TEXT,INDEX); //SKIP SPACE 数値前のスペース

      //数値取得
      if GetValue(TEXT,INDEX,SELECT_TEXT) = true then
      begin
        if SelTextToFloat(SELECT_TEXT,VALUE) = True then
        begin

          //-------------------------
          // 四則演算 + - * / \
          //-------------------------
          case Operation of
            '+':ARESULT := ARESULT + VALUE;
            '-':ARESULT := ARESULT - VALUE;
            '/':ARESULT := ARESULT / VALUE;
            '*':ARESULT := ARESULT * VALUE;
            '\':ARESULT := Extended(Trunc(ARESULT) mod Trunc(VALUE));
            '^':ARESULT := Extended(Power(ARESULT,Trunc(VALUE)));
            else
            ARESULT := VALUE;
          end;

          SkipSpace(TEXT,INDEX); //SKIP SPACE 数値と四則演算の間のスペース

          if INDEX <= TEXT_LENGTH  then
          begin
            C := TEXT[Index];
            if (C = '+') or (C= '-') or (C='*') or (C='/') or (C = '\') or (C = '^') then
            begin
              Inc(INDEX); //Operation の次の位置から
              XXX(INDEX,TEXT,C,ARESULT,ERROR);
            end
            else
            begin
             //四則演算が + - * / \ ^ 以外
              ERROR := 2;
            end;
          end
          else
          begin
            //テキスト最後
            ERROR := 0;
          end;
        end
        else
        begin
          //ERROR <> 0
        end;

      end
      else
      begin
        //数値が無い
        ERROR := 3;
      end;
   end
   else
   begin
     //ERROR <> 0
   end;
end;

// * と / を計算
function YYY(TEXT: String; var ERROR: Integer): String;
var
    C: Char;
    INDEX,INDEX1,INDEX2,INDEX3,INDEX4: Integer;
    SAVE_INDEX,SAVE_INDEX1,SAVE_INDEX2: Integer;
    SELECT_TEXT1,SELECT_TEXT2: String;
    E1,E2: Extended;
    S1,S2: String;
begin
    RESULT := TEXT;

    while (Pos('*',TEXT) <> 0) and (ERROR = 0) do
    begin

      INDEX1 := Pos('*',TEXT);
      INDEX2 := Pos('/',TEXT);
      INDEX3 := Pos('^',TEXT);
      INDEX4 := Pos('\',TEXT);

      if (INDEX1 or INDEX2 or INDEX3 or INDEX4) <> 0 then
      begin

        C := Char(0);
        if (INDEX2 or INDEX3 or INDEX4) < INDEX1 then
        begin
          INDEX := INDEX1;
          C := '*';
        end;
        if (INDEX1 or INDEX3 or INDEX4) < INDEX2 then
        begin
          INDEX := INDEX2;
          C := '/';
        end;
        if (INDEX1 or INDEX2 or INDEX4) < INDEX3 then
        begin
          INDEX := INDEX3;
          C := '^';
        end;
        if (INDEX1 or INDEX2 or INDEX3) < INDEX4 then
        begin
          INDEX := INDEX4;
          C := '\';
        end;

        SAVE_INDEX := INDEX;

        //* - の前の値
        Dec(INDEX);
        SkipSpace2(TEXT,INDEX);
        GetValue2(TEXT,INDEX,SELECT_TEXT1);
        SAVE_INDEX1 := INDEX;

        //* - の後ろの値
        INDEX := SAVE_INDEX;
        Inc(INDEX);
        SkipSpace(TEXT,INDEX);
        GetValue(TEXT,INDEX,SELECT_TEXT2);
        SAVE_INDEX2 := INDEX;

        S1 := Copy(TEXT,1,SAVE_INDEX1-1);
        S2 := Copy(TEXT,SAVE_INDEX2);

        if C = '^' then
        begin
          if SelTextToFloat(SELECT_TEXT1,E1) = True then
          begin
            try
              TEXT := S1 + FloatToStr(Power(E2,StrToInt(SELECT_TEXT2))) + S2;
            except
              ERROR := 99;
            end;
          end
          else
          begin
            ERROR := 99;
          end;
        end
        else
        begin
          if SelTextToFloat(SELECT_TEXT1,E1) = True then
          begin
            if SelTextToFloat(SELECT_TEXT2,E2) = True then
            begin
              if C = '*' then TEXT := S1 + FloatToStr(E1 * E2) + S2;
              if C = '/' then TEXT := S1 + FloatToStr(E2 / E2) + S2;
              if C = '\' then TEXT := S1 + FloatToStr(Trunc(E1) mod Trunc(E2)) + S2;
            end
            else
            begin
              ERROR := 99;
            end;
          end
          else
          begin
            ERROR := 99;
          end;
        end;

      end;
    end;

    RESULT := TEXT;
end;

function ZZZ(TEXT: String; var ERROR: Integer): Extended;
var
    Index1,Index2: Integer;
    SELECT_TEXT: String;
    ARESULT: Extended;
begin

    if ERROR = 0 then
    begin

      while (Pos('(',TEXT) <> 0) and (ERROR = 0) do
      begin
        Index1 := LastPos('(',TEXT);
        if Index1 <> 0 then
        begin
          Index2 := Pos(')',TEXT,Index1) + 1;
          if Index2 <> 0 then
          begin
            SELECT_TEXT := Copy(TEXT,Index1+1,Index2 - Index1-2);  // () 内のテキスト
            //* / ^ \ を先行計算
            SELECT_TEXT := YYY(SELECT_TEXT,ERROR);
            //() 内を計算させる
            ARESULT := 0;
            ERROR   := 0;
            XXX(1,SELECT_TEXT,Char(0),ARESULT,ERROR);
            //() 内を置き換える
            TEXT := Replace(TEXT,Index1,Index2,FloatToStr(ARESULT));
          end
          else
          begin
            //)が無い
            ERROR := 5;
          end;
        end;
      end;

      // () の無くなったテキストを計算
      if ERROR = 0 then
      begin
        RESULT := 0;
        //* / ^ \ を先行計算
        TEXT := YYY(TEXT,ERROR);
        XXX(1,TEXT,Char(0),RESULT,ERROR);
      end;

    end;

end;

//MEMO1 に式
//MEMO2 に結果
procedure TForm1.Button1Click(Sender: TObject);
var
    RESULT: Extended;
    ERROR: Integer;
    I: Integer;
begin
    Memo2.Clear;
    for I:=0 to Memo1.Lines.Count -1 do
    begin
      ERROR := 0;
      RESULT := ZZZ (Memo1.Lines[I],ERROR);
      if ERROR = 0 then
      begin
        Memo2.Lines.Add(FloatToStr(RESULT));
      end
      else
      begin
        Memo2.Lines.Add('ERROR:' + IntToStr(ERROR));
      end;
    end;
end;


take  2020-11-24 09:24:06  No: 149398

自分だったら外部スクリプトに計算を投げて結果を受け取る方が早いと思うけどどうでしょ?
一昔前ならDelphiでDelphiをインタプリタ実行する PPA(project-PPA)などを使ってたかも

簡単そうに思われてる人いるかも知れませんがこの出題はコンピュータ系学校の卒論レベルですよ。
ここから逆ポーランド記法にハマったり、この入力が出来る関数電卓に慣れて普通の電卓に戻れなくなったりします。


※返信する前に利用規約をご確認ください。








  このエントリーをはてなブックマークに追加