前提・実現したいこと
C言語で電卓を作っています。たとえば1+3*(2+4)のように、一桁の正整数、加算、乗算、括弧(入れ子なし)からなる式が入力された時、計算結果を画面に表示するプログラムを作りたいです。
ここに質問の内容を詳しく書いてください。
入力された式は一旦文字列型の配列に格納し、その後整数型の配列に変換します。この際、後で探索がしやすいように'('は-1,')'は-2,'+'は-3,'*'は-4として変換しています。
ここからが問題で、たとえば上の式であれば、まず(2+4)を計算すると思います。そのように、選択した部分の計算を実行するにはどうしたら良いでしょうか。()や+,といった演算子に応じて範囲を選択し、該当部分の計算を実行するというアルゴリズムでいこうと思っています。
まずは()を探索し、()内の加算、乗算を行います。
→ここで役目を終えた括弧を消去したいのですが、それはどのようにしたら良いでしょうか。
次に加算、乗算についてです。
たとえば
a[i]の演算子が+だった場合、a[i-1]=a[i-1]+a[i+1];
a[i]の演算子がだった場合、a[i-1]=a[i-1]a[i+1];
とすれば計算結果は得られますが、この後の処理はどうすれば良いでしょうか。
配列を左にどんどん詰めていくというのが基本的な考え方だと思うのですが、ではどのタイミングで詰めるのかがわかりません。
演算子がなくなるまで乗算を実行、その後演算子+がなくなるまで加算を実行というプロセスでいいことはわかります。
どの処理を関数として定義するかが全くわかりません。
ご回答のほどよろしくお願いいたします。
■■な機能を実装中に以下のエラーメッセージが発生しました。
発生している問題・エラーメッセージ
エラーメッセージ
該当のソースコード
#include <stdio.h>
int main(void)
{
char formula1[100];
printf("式を入力してください:" );
gets(formula1);//ひとまず文字型の配列に数字等を格納
//型の変換
int formula2[100];//変換後の数値、記号を格納する配列
for (int i = 0; i < 100; i++) {
if(formula1[i]=='('){formula2[i]=-1;}
else
if(formula1[i]==')'){formula2[i]=-2;}
else
if(formula1[i]=='+'){formula2[i]=-3;}
else
if(formula1[i]=='*'){formula2[i]=-4;}
else
{formula2[i]=formula1[i]-'0';}//数字なら数値に変換
}
}
c言語```ここに言語名を入力
ソースコード
### 試したこと ここに問題に対して試したことを記載してください。 ### 補足情報(FW/ツールのバージョンなど) ここにより詳細な情報を記載してください。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/10/25 23:29
2021/10/26 18:11 編集
回答5件
0
ベストアンサー
正規表現を使えばこうなるでしょう。
c
1#include <stdio.h> 2#include <stdlib.h> 3#include <string.h> 4#include <regex.h> 5 6regex_t preg[3]; 7 8void replace(char *f, int so, int eo, int v) { 9 sprintf(&f[so], "%d", v); 10 strcat(f, &f[eo]); 11} 12 13int calc(char *f) { 14 regmatch_t match[3]; 15 // () 16 while(!regexec(&preg[0], f, 1, match, 0)) { 17 f[(int)match[0].rm_eo-1] = '\0'; 18 int v = calc(&f[match[0].rm_so+1]); 19 replace(f, match[0].rm_so, match[0].rm_eo, v); 20 } 21 // * 22 while(!regexec(&preg[1], f, 3, match, 0)) { 23 int v1 = (int)strtol(&f[match[1].rm_so], NULL, 10); 24 int v2 = (int)strtol(&f[match[2].rm_so], NULL, 10); 25 int v = v1 * v2; 26 replace(f, match[0].rm_so, match[0].rm_eo, v); 27 } 28 // + 29 while(!regexec(&preg[2], f, 3, match, 0)) { 30 int v1 = (int)strtol(&f[match[1].rm_so], NULL, 10); 31 int v2 = (int)strtol(&f[match[2].rm_so], NULL, 10); 32 int v = v1 + v2; 33 replace(f, match[0].rm_so, match[0].rm_eo, v); 34 } 35 return atoi(f); 36} 37 38void test(char *formula) { 39 printf("f=%s\n", formula); 40 41 regcomp(&preg[0], "\([^(]+\)", REG_EXTENDED|REG_NEWLINE); // () 42 regcomp(&preg[1], "([0-9]+)\*([0-9]+)", REG_EXTENDED|REG_NEWLINE); // * 43 regcomp(&preg[2], "([0-9]+)\+([0-9]+)", REG_EXTENDED|REG_NEWLINE); // + 44 45 char *f = malloc(strlen(formula)+1); 46 strcpy(f, formula); 47 48 int a = calc(f); 49 printf("a=%d\n", a); 50 51 free(f); 52 53 regfree(&preg[2]); 54 regfree(&preg[1]); 55 regfree(&preg[0]); 56} 57 58int main(int argc, char *argv[]) { 59 test("1+2"); 60 test("2*3"); 61 test("1+2*3"); 62 test("(1+2)*3"); 63 test("1+3*(2+4)"); 64}
plain
1f=1+2 2a=3 3f=2*3 4a=6 5f=1+2*3 6a=7 7f=(1+2)*3 8a=9 9f=1+3*(2+4) 10a=19
参考までに、スタックでベタに逆ポーランド記法利用時はこんな感じかと。
c
1#include <stdio.h> 2#include <stdlib.h> 3#include <string.h> 4#include <ctype.h> 5#include <limits.h> 6 7/* 8 * スタック 9 */ 10 11#define STACK_MAX 100 12int istack[STACK_MAX]; 13char *cstack[STACK_MAX]; 14int sp; 15 16void cpush(char *token) { 17 cstack[sp++] = token; 18} 19 20char *cpop() { 21 return sp > 0 ? cstack[--sp] : NULL; 22} 23 24char *cpeek() { 25 return sp > 0 ? cstack[sp-1] : NULL; 26} 27 28void ipush(int value) { 29 istack[sp++] = value; 30} 31 32int ipop() { 33 return sp > 0 ? istack[--sp] : INT_MAX; 34} 35 36/* 37 * 式分解(一文字ずつ文字列化) 38 */ 39 40#define BUFFER_MAX 1024 41char buf[BUFFER_MAX]; 42 43char **splitFormula(char *formula) { 44 memset(buf, 0, sizeof(buf)); 45 char **tokens = malloc((strlen(formula)+1)*sizeof(char*)); 46 char *p = buf; 47 for(int i=0; i<strlen(formula); i++) { 48 *p = formula[i]; 49 tokens[i] = p; 50 p += 2; 51 } 52 tokens[strlen(formula)] = NULL; 53 return tokens; 54} 55 56/* 57 * RPN化 58 */ 59 60int priority(char *op) { 61 if(*op == '*') return 1; 62 if(*op == '+') return 0; 63 return -1; 64} 65 66void toRPN(char *tokens[]) { 67 sp = 0; 68 int j = 0; 69 for(int i=0; tokens[i]!=NULL; i++) { 70 char *token = tokens[i]; 71 if(isdigit((int)*token)) { 72 tokens[j++] = token; 73 } else if(*token == '(') { 74 cpush(token); 75 } else if(*token == ')') { 76 for (char *t; (t=cpop())!=NULL && *t!='('; ) tokens[j++] = t; 77 } else { 78 while(cpeek()!=NULL && priority(cpeek()) >= priority(token)) tokens[j++] = cpop(); 79 cpush(token); 80 } 81 } 82 for (char *t; (t=cpop())!=NULL; ) tokens[j++] = t; 83 tokens[j] = NULL; 84} 85 86/* 87 * 計算 88 */ 89 90int calc(char *rpn[]) { 91 sp = 0; 92 for(int i=0; rpn[i]!=NULL; i++) { 93 if(isdigit((int)*rpn[i])) { 94 ipush(atoi(rpn[i])); 95 } else { 96 int v2 = ipop(); 97 int v1 = ipop(); 98 if(*rpn[i] == '*') { 99 ipush(v1 * v2); 100 } else if(*rpn[i] == '+') { 101 ipush(v1 + v2); 102 } 103 } 104 } 105 return ipop(); 106} 107 108/* 109 * 確認用 110 */ 111 112void printTokens(char *tokens[]) { 113 for(int i=0; tokens[i]!=NULL; i++) printf(" %s", tokens[i]); 114 printf("\n"); 115} 116 117void test(char *formula) { 118 char **expr = splitFormula(formula); 119 printTokens(expr); 120 toRPN(expr); 121 printTokens(expr); 122 printf("%d\n", calc(expr)); 123 free(expr); 124} 125 126int main(int argc, char *argv[]) { 127 test("1+2"); 128 test("2*3"); 129 test("1+2*3"); 130 test("(1+2)*3"); 131 test("1+3*(2+4)"); 132}
plain
1 1 + 2 2 1 2 + 33 4 2 * 3 5 2 3 * 66 7 1 + 2 * 3 8 1 2 3 * + 97 10 ( 1 + 2 ) * 3 11 1 2 + 3 * 129 13 1 + 3 * ( 2 + 4 ) 14 1 3 2 4 + * + 1519
投稿2021/10/26 13:26
編集2021/10/27 10:56総合スコア13209
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
いろいろなやり方があります。たとえば、これはどうでしょうか?
C
1#include <stdio.h> // fgets, printf 2 3char a[1000]; 4int i, c, expr(void); 5 6int factor(void) 7{ 8 int v = 0; 9 c = a[i++]; 10 if (c == '(') { 11 v = expr(); 12 if (c == ')') c = a[i++]; 13 else c = 1; 14 } 15 else if (c >= '0' && c <= '9') { 16 v = c - '0'; 17 c = a[i++]; 18 } 19 else c = 1; 20 return v; 21} 22 23int term(void) 24{ 25 int v = factor(); 26 while (c == '*') v *= factor(); 27 return v; 28} 29 30int expr(void) 31{ 32 int v = term(); 33 while (c == '+') v += term(); 34 return v; 35} 36 37int main(void) 38{ 39 fgets(a, sizeof a, stdin); 40 int v = expr(); 41 if (c == '\n' || c == 0) printf("%d\n", v); 42 else puts("error"); 43}
ポインタも構造体もないから、むずかしくないと思うんですが、どうでしょうか?
追記
逆ポーランド記法に変換してから計算するやり方です。
C
1#include <stdio.h> // puts, printf 2 3const char *a; char b[1000]; int i, j, c; void expr(void); 4 5void factor(void) 6{ 7 c = a[i++]; 8 if (c == '(') expr(), c = c == ')' ? a[i++] : 1; 9 else if (c - '0' <= 9u) b[j++] = c, c = a[i++]; 10 else c = 1; 11} 12 13void term(void) { for (factor(); c == '*'; b[j++] = '*') factor(); } 14 15void expr(void) { for (term(); c == '+'; b[j++] = '+') term(); } 16 17int calc(void) 18{ 19 int s[100], p = 0; // s: stack, p: stack pointer 20 for (int k = 0; k < j; k++) 21 if (b[k] == '+') p--, s[p-1] += s[p]; 22 else if (b[k] == '*') p--, s[p-1] *= s[p]; 23 else s[p++] = b[k] - '0'; 24 return s[0]; 25} 26 27void test(const char *s) 28{ 29 puts(a = s); 30 i = j = 0; 31 expr(); 32 if (c) puts(" error"); 33 else { 34 for (int k = 0; k < j; k++) printf(" %c", b[k]); 35 printf(" : %d\n", calc()); 36 } 37} 38 39int main(void) 40{ 41 test("1+2"); 42 test("2*3"); 43 test("1+2*3"); 44 test("(1+2)*3"); 45 test("1+3*(2+4)"); 46}
投稿2021/10/26 08:05
編集2021/10/27 10:35総合スコア8224
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
()や+,*といった演算子に応じて範囲を選択し、該当部分の計算を実行する
という話を,「計算を実行する範囲を2つのポインタで示す」という形でやってみました.
話を簡単にするため,メモリを動的に確保とかはせず,作業用のバッファは「十分なサイズだけ」ローカル変数で用意する形にしてあります.
また,
一桁の正整数、加算、乗算、括弧(入れ子なし)
という条件であることを前提とし,エラーチェックはまともに入ってません.
C
1//文字列の一部.範囲 [begin, end) として表す 2typedef struct SubStr 3{ 4 const char *begin; //先頭の文字を指す 5 const char *end; //末尾の文字の次を指す 6} SubStr; 7 8//(プロトタイプ宣言) 9int EvalStr( const char *begin, const char *end ); 10 11//[begin, end) の範囲から c を探してその場所を返す. 12//見つからない場合は end を返す. 13const char *find( char c, const char *begin, const char *end ) 14{ 15 const char *p = begin; 16 for( ; (p!=end) && ((*p)!=c); ++p ){} 17 return p; 18} 19 20//文字列 [begin, end) をバラす. 21//ただし,括弧で囲まれた範囲の内側まではバラさらない. 22//結果は pBuff の先頭側から突っ込まれる.(pBuff は十分なサイズの領域を指すこと) 23//突っ込んだ個数を返す. 24int ParseToSubStrs( SubStr *pBuff, const char *begin, const char *end ) 25{ 26 int n = 0; 27 const char *p = begin; 28 while( p != end ) 29 { 30 if ( *p == '(' ) 31 {// '(' と ')' で囲まれた範囲(括弧の内側)を1つの結果とする 32 const char *pRightParenthese = find( ')', p+1, end ); 33 if( pRightParenthese == end ) 34 { 35 printf( "Error : Left_parenthese without Right_parenthese" ); 36 exit(0); 37 } 38 pBuff[n].begin = p+1; 39 pBuff[n].end = pRightParenthese; 40 p = pRightParenthese + 1; 41 } 42 else 43 {//※ここに来るとき,pが指すのは,数字(一桁限定なので1文字)か演算子(1文字)であるハズ 44 pBuff[n].begin = p; 45 pBuff[n].end = p+1; 46 ++p; 47 } 48 ++n; 49 } 50 return n; 51} 52 53//単一の SubStr の内容を整数値に解釈する. 54int EvalSubStr_to_Int( SubStr SS ) 55{ 56 if( SS.end - SS.begin > 1 ) 57 { return EvalStr( SS.begin, SS.end ); } 58 else 59 { return *SS.begin - '0'; } //※1桁の数値であることを決め打ちで処理 60} 61 62// nSS 個の連続した SubStr 群を整数値に解釈する. 63int EvalSubStrs( SubStr *pSSs, int nSS ) 64{ 65 if( nSS == 1 ) 66 { return EvalSubStr_to_Int( pSSs[0] ); } 67 68 for( int i=1; i<nSS-1; ++i ) 69 { 70 if( *pSSs[i].begin == '+' ) 71 { 72 int Left = EvalSubStrs( pSSs, i ); 73 int Right = EvalSubStrs( pSSs+(i+1), nSS-(i+1) ); 74 return Left + Right; 75 } 76 } 77 78 for( int i=1; i<nSS-1; ++i ) 79 { 80 if( *pSSs[i].begin == '*' ) 81 { 82 int Left = EvalSubStrs( pSSs, i ); 83 int Right = EvalSubStrs( pSSs+(i+1), nSS-(i+1) ); 84 return Left * Right; 85 } 86 } 87 printf( "Error\n" ); 88 exit( 0 ); 89} 90 91//数式文字列 [begin, end) の内容を評価して結果値を返す. 92int EvalStr( const char *begin, const char *end ) 93{ 94 SubStr SubStrs[16]; //※サイズはてきとー 95 int nSubStr = ParseToSubStrs( SubStrs, begin, end ); //文字列をバラして… 96 return EvalSubStrs( SubStrs, nSubStr ); //評価する 97} 98 99//--------------- 100 101//式1個分をやってみるテスト 102void Test( const char *Str ) 103{ 104 int n = (int)strlen( Str ); 105 printf( "%s = %d\n", Str, EvalStr( Str, Str+n ) ); 106} 107 108int main(void) 109{//いろんな式でテスト 110 Test( "1+3*(2+4)" ); 111 Test( "(3+9)*(2*4)" ); 112 Test( "4+7*2" ); 113 Test( "4*1*5+2" ); 114 return 0; 115}
投稿2021/10/26 07:33
編集2021/10/26 07:46総合スコア11996
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
まず、プログラミングは「こう書けばいい」というものではありません。
現実世界のシミュレーションです。
(おそらく質問者さんはお気づきかもしれませんが)
なので、現実世界でならどうするかを考えるのです。
今回の場合、割と楽な気がします。
かっこが使われていますが、入れ子はなく、かつ数字は常に一桁であることが前提のようですから。
F = 1+3*(2+4)
とします。
このFを計算する場合、現実世界ではどのようにしますか?
私なら、『かっこから先に計算』します。
1+3*(2+4) = 1+3*6
ということは、『(…)で囲まれている式を先に計算』するということです。
構造をよくみてください。必ず、“(”と“)”の数は同じであるはずです。そして、入れ子ではない。
ということは、
1. 先頭から末尾までの中で最初の“(“を探す 2. “(”があったら 2.1. “)”の場所を探す 2.2. (1)の場所から(2.1)の場所まで切り出す 2.3. 計算する 2.4. 置き換える
のような感じになるかと思います。
…発想そのものはここまでできているようですね。
あとは実現方法。
現実世界で考えてみてください。質問者さんは脳内ではどのようにやっていますか?
おそらく、かっこの中を先に計算して、そこをYとかのような別の値に変換しているのではないかなと。
F = 1+3*(2+4) 一番優先順位の高い計算はかっこ付きなので”(“を探す。それで囲まれている部分を探す。 (2+4)の計算結果をYと置くと、 F = 1+3*Y と置き換える。 これ以上かっこ付きはないので次の演算子へ。次に優先順位の高いものは掛け算なので、 *を探す。そうすると3*Yがヒット。これを計算する。 この計算結果をXと置くと、 F = 1+X となる。これ以上優先するものはないので以降は先端から計算していく。
的な感じになっているはずです。
もちろん、実際に使う文字や式とかは違いますが。
Yとかを使わずに数字にするのが普通かもしれませんが。
とにかく、『元の計算式の特定の部分を書き換える』的な処理を脳内でしているはずです。
上記で言えば(2+4) を Y とおく感じの。実際にはYではなく、2+4の結果、6と置き換える感じです。
アルゴリズムっぽく考えると、
1. “(”の場所を調べる(int beginとする) 2. “)”の場所を調べる(int endとする) 3. beginからendまでの文字列を切り出す 4. (3)を計算(int tempとする) 5. 元の文字列のbeginからendまでを(4)の値に置き換える これを優先順位が高いものがなくなるまで繰り返す
的な感じになるかなと。
つまり、『切り出し』→『計算』→『元の文字列に置き換え』的な処理ですね。入れ子になっていると面倒ですが、それでも発想は同じかも。
[追記0]
あと、質問では“+”とかを-1やらなんやらに置き換えていますが、それはしなくてもできるはずです。
式全体をFとしたとき、Fは単なる文字列です。
文字列は文字が0文字以上連なったものです。
だから配列で表現するのです。
“1”も“+”も文字です。
それに、出てくる数字に関しては(かっこ内でないなら)常に一桁ですよね。
ということは、
<数字><演算子><数字><演算子>…
となっているはずです。
よくみると、数字と演算子が交互に出ています。となると、かっこ内をすでに計算した後という前提であれば、
1. 文字数分ループ 1.1. 現在の文字を取得 1.2. 現在の文字が“+”なら 1.2.1. 前後の文字をそれぞれ整数にして 1.2.2. 計算する 1.3. (そうじゃなくて) “-”なら …
のような感じになるかなと。
ただし、”1”と 1は別物です。なので『文字を整数にする処理』が必要になります。文字列ではありません。
これに関しては調べてください。
上で切り出したかっこ内の式もこの考えを適用できます。
ーーーー
[追記0:オフトピ]
修正依頼のところに書くべき内容ですが、質問者さんは無視しているのか、やり方がわからないのかわかりませんが、改善されていないようなので書いておきます。
コードは”<code>”または”<コード>”のボタンを押して出てくるやつの中にかきましょう。
ご自分の質問にあるコードと回答者のコード等を比較してみましょう。今のままでは非常に読みづらいです。ひらがなでしかも句読点なしの文章を読まされるようなものです。
知っていて我を押し通すつもりなら次からは回答者が一気に減ると思います。そうすると質問者さんにとって有用な情報も得られなくなります。
知らなかったのなら、これを機にやってみましょう。質問は修正できるので修正してください。
[私が「修正依頼を無視している」と考えた根拠]
過去質問(一件ですが)では、ある方がご指摘なさっていますが、それを無視しています。
もしかしたらMarkdownとかの存在がわからない可能性もあるとは思いますが、流石にアニメーションで提示されているのでそれは無いかなと。
仮に「質問を編集できるということが分かっていなかった」だけだとしても、二回目以降の質問でも同じようなことをやっています。なので「改善する気が無い」人なのかなと勘繰ってみたり。
※このオフトピ追記は質問が修正され次第、削除する予定です。
投稿2021/10/25 22:30
編集2021/10/26 08:14総合スコア4962
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2021/10/26 00:56
2021/10/26 01:06
2021/10/26 01:13
2021/10/26 01:25
2021/10/26 01:28
2021/10/26 02:21 編集
2021/10/26 02:29
2021/10/26 02:31
2021/10/26 02:32
2021/10/26 02:32
0
演算子の優先順位やカッコに対応した電卓はスタックを使って実装するとやりやすいと思います。
その際、計算対象の数式を逆ポーランド記法という書き方に変換することで演算子の優先順位などの煩わしさを軽減できます。
以下の記事はコードがPerlで書かれているものの、電卓のアルゴリズムについてわかりやすく解説されていますので、よければご一読ください。
ご質問いただいたアルゴリズムが複雑でその方法で実装できるか自信がないため、質問に対する直接の回答ができず申し訳ありませんが、ご参考まで。
投稿2021/10/25 21:05
総合スコア752
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。