ファイルに書かれた式の計算について

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
bucchus
記事: 2
登録日時: 3年前

ファイルに書かれた式の計算について

#1

投稿記事 by bucchus » 3年前

お世話になります。
学校の課題で下記を出されました。
----------------------------------------
問 次に示す内容のファイルを作成し、
file start-----
1 + 1 =
10 + 20 =
100 + 20 - 50 =
5 * 0 - 3 + 5 =
2 - 3 / 2 + 0.5 =
-----End of File(EOF)
これを読み込み、ファイルに記述された式を演算して、
式と演算結果を画面に表示する実行可能なプログラムを作成せよ。
----------------------------------------
ここまで

「ファイル操作」については、
・fpopenの使い方
・fputs()関数、fgets()関数、fscanf()関数の使い方
・テキストファイルに記された数値を読み込み、最大と最小を求める
ことは学びました。

ただ、ファイルに入っている記号「* / + - =」の扱いがわかりません。

ファイル読み出しのときに配列に入れて、*の記号があったときに
「掛け算」をするのかな??
と思っています。(推測ですが。。)

簡単かと思いますが、教えてください!
よろしくお願いいたします。

コード:

 
#include<stdio.h>
#define MAX 1024
int main(void){
    FILE *fp ;
    char buf[MAX]={0};
    if((fp=fopen("test.txt","r"))!=NULL){
        /*ファイルの終わりまで繰り返し読み込む*/
        while( fgets(buf,MAX,fp) != NULL ){
            printf("%s",buf);
        }
    }
    return 0;
}

アバター
みけCAT
記事: 6246
登録日時: 9年前
住所: 千葉県
連絡を取る:

Re: ファイルに書かれた式の計算について

#2

投稿記事 by みけCAT » 3年前

bucchus さんが書きました:ファイルに記述された式を演算して
普通の優先順位に従った計算ですか?
それとも、100均で売っているような電卓のように単純に前から計算するのですか?
bucchus さんが書きました:ただ、ファイルに入っている記号「* / + - =」の扱いがわかりません。
記号以外、すなわち小数点を含む数字の扱いはわかるのですか?
できるところまでコードを書いていただけると答えやすいです。
bucchus さんが書きました:ファイル読み出しのときに配列に入れて、*の記号があったときに
「掛け算」をするのかな??
と思っています。(推測ですが。。)
*の記号を読んだ時点では、掛ける数を読んでいないので「掛け算」はできません。
優先順位をきちんと考える場合、読んだ数字や演算子を配列に格納しながら処理するのはいい方針の一つでしょう。
オフトピック
bucchus さんが書きました:式と演算結果を画面に表示する実行可能なプログラムを作成せよ。
わざわざ実行可能でないプログラムを作る、もしくは課題で作らせることはあるのだろうか?
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

bucchus
記事: 2
登録日時: 3年前

Re: ファイルに書かれた式の計算について

#3

投稿記事 by bucchus » 3年前

みけCAT様

返信ありがとうございます!
そうですよね。計算の順序は考える要素ですね。
とりあえず、
----------------
1 + 1 =
10 + 20 =
100 + 20 - 50 =
----------------
これだけでもできたらと思っています。
これならば単純に前から計算できそうです。
また、数字もint型で済むのでやりやすそうです。

今、配列に入れた「1 + 1 =」をどうやって計算するのかを考えています。

アバター
usao
記事: 1566
登録日時: 6年前

Re: ファイルに書かれた式の計算について

#4

投稿記事 by usao » 3年前

>今、配列に入れた「1 + 1 =」をどうやって計算するのかを考えています。

人間が計算していく手順と同じことをすればよいのではないでしょうか.
例えば…

2 - 3 / 2 + 0.5 =

2 - 1.5 + 0.5 =

0.5 + 0.5 =

1.0

これの各段階で「何をやっているか」を具体的に言えれば
それが実装すべき処理内容になると思います.

アバター
みけCAT
記事: 6246
登録日時: 9年前
住所: 千葉県
連絡を取る:

Re: ファイルに書かれた式の計算について

#5

投稿記事 by みけCAT » 3年前

usao さんが書きました:>今、配列に入れた「1 + 1 =」をどうやって計算するのかを考えています。

人間が計算していく手順と同じことをすればよいのではないでしょうか.
わざわざそのように式の途中を変換していくのはめんどくさいでしょう。
優先順位を考えるなら、式を一旦逆ポーランド記法に変換してから計算するといいでしょう。

数式を逆ポーランド法に変換するための事柄

計算してみる
逆ポーランド記法への変換1
逆ポーランド記法への変換2
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

box
記事: 1745
登録日時: 9年前

Re: ファイルに書かれた式の計算について

#6

投稿記事 by box » 3年前

まあRPNを使うことは他の回答者さんの回答のとおりだと思います。
それにしてもむずかしい課題だな…。質問者さんのC言語のレベルがどの程度かはわかりませんが…。
バグのないプログラムはない。
プログラムは思ったとおりには動かない。書いたとおりに動く。

かずま

Re: ファイルに書かれた式の計算について

#7

投稿記事 by かずま » 3年前

逆ポーランド記法を使うと面倒です。
逆ポーランド記法を使わないほうが簡単になると思います

コード:

#include <stdio.h>  // fopen, fclose, fgets, fputs, printf, puts, sscanf
#include <ctype.h>  // isspace, isdigit

#define MAX 1024

char *p;
unsigned char c;

int get(void)
{
    while (isspace(c = *p++)) ;
    return c;
}

double factor(void)
{
    int n;
    double v = 0;
    if (isdigit(get())) {
        sscanf(--p, "%lf%n", &v, &n);
        p += n;
        get();
    }
    else
        c = 1;
    return v;
}

double term(void)
{
    double v = factor();
    while (1)
        if (c == '*')
            v *= factor();
        else if (c == '/')
            v /= factor();
        else return v;
}

double expr(void)
{
    double v = term();
    while (1)
        if (c == '+')
            v += term();
        else if (c == '-')
            v -= term();
        else return v;
}

int calc(char *b, double *v)
{
    p = b;
    *v = expr();
    return c == '=';
}

int main(void)
{
    char buf[MAX];
    FILE *fp = fopen("test.txt", "r");
    if (fp == NULL) return 1;
    while (fgets(buf, MAX, fp)) {
        double v;
        fputs(buf, stdout);
        if (calc(buf, &v))
            printf("  %.15g\n", v);
        else
            puts("  error");
    }
    fclose(fp);
    return 0;
}
理解できたら、あなた自身の書き方に書き直して、ここに提示してください。
理解できないなら、どこが分からないのかを質問してください。

アバター
usao
記事: 1566
登録日時: 6年前

Re: ファイルに書かれた式の計算について

#8

投稿記事 by usao » 3年前

オフトピック
>わざわざそのように式の途中を変換していくのはめんどくさいでしょう
>優先順位を考えるなら、式を一旦逆ポーランド記法に変換してから計算するといいでしょう。


個人的には,処理すべき数式が特定の5つに限定されているという「ザ・課題」な状況下において
わざわざ

>逆ポーランド記法に変換

をやるとか圧倒的に面倒(というか,おおげさというか)だと思ってしまうのだけど…

アバター
みけCAT
記事: 6246
登録日時: 9年前
住所: 千葉県
連絡を取る:

Re: ファイルに書かれた式の計算について

#9

投稿記事 by みけCAT » 3年前

usao さんが書きました:個人的には,処理すべき数式が特定の5つに限定されているという「ザ・課題」な状況下において
わざわざ

>逆ポーランド記法に変換

をやるとか圧倒的に面倒(というか,おおげさというか)だと思ってしまうのだけど…
なるほど、確かにそうですね。
課題で作成するべきプログラムの内容は
「指定された内容のファイルを作成し」
「これを読み込み」
「ファイルに記述された式を演算して」
「式と演算結果を画面に表示する」
であり、ファイルシステムで異常が起きなければファイルの内容はわかっているので、わざわざファイルの内容を処理する必要は無いですね。

コード:

#include <stdio.h>

void create_file(void) {
    /* ファイルを作成する */
}

void read_file(void) {
    /* ファイルを読み捨てる */
}

void calc_and_print(void) {
    /* 計算と出力をする */
    printf("1 + 1 = %d\n", 1 + 1);
    /* 他の式も同様 */
}

int main(void) {
    create_file();
    read_file();
    calc_and_print();
    return 0;
}
とすればいいのですね。
printf()に渡すデータの型と変換指定子の対応を間違えて未定義動作を起こさないように気をつけましょう。
読み捨てるかわりに、ファイルを正しく作れたかベリファイしてもいいでしょう。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

たいちう
記事: 418
登録日時: 9年前

Re: ファイルに書かれた式の計算について

#10

投稿記事 by たいちう » 3年前

みけCAT さんが書きました: 課題で作成するべきプログラムの内容は
「指定された内容のファイルを作成し」
ファイルの作成をプログラムでする必要はないんじゃないですか?

かずま

Re: ファイルに書かれた式の計算について

#11

投稿記事 by かずま » 3年前

かずま さんが書きました:逆ポーランド記法を使うと面倒です。
逆ポーランド記法を使わないほうが簡単になると思います
このプログラムでは、数を表す文字列を数値に変換するのに sscanf を
使いましたが、<stdlib.h> にある strtod を使うほうがよいでしょう。
また、term と expr は形がよく似ているので、
一つにまとめられないかと考えて、次のように書き換えてみました。

コード:

#include <stdio.h>  // fopen, fclose, fgets, fputs, printf, puts
#include <stdlib.h> // strtod
#include <ctype.h>  // isspace, isdigit

unsigned char c;
char *p;

int get(void) { while (isspace(c = *p++)) ; return c; }

double expr(const char *b)
{
    double v;
    if (*b)
        for (v = expr(b+2); c == b[0] || c == b[1]; )
            c == '+' ? v += expr(b+2) :
            c == '-' ? v -= expr(b+2) :
            c == '*' ? v *= expr(b+2) : (v /= expr(b+2));
    else isdigit(get()) ? v = strtod(p-1, &p), get() : (v = c = 1);
    return v;
}

int calc(char *s, double *v) { return p = s, *v = expr("+-*/"), c == '='; }

int main(void)
{
    double v;
    char buf[1024];
    FILE *fp = fopen("test.txt", "r");
    if (fp == NULL) return 1;
    while (fgets(buf, sizeof buf, fp))
        fputs(buf, stdout), calc(buf, &v) ? printf("  %.15g\n", v): puts("  error");
    fclose(fp);
    return 0;
}
さて、逆ポーランド記法を使うほうが良いと主張される方は、どうしてそうなのかを
説明していただけないでしょうか?
この中に、次のような記述があります。
▼ まず数学のルールとして演算子の優先順位を考えます

+と-は優先順位は同じ
*は+と-より優先順位が高い
/は*より優先順位が高い
「/は*より優先順位が高い」は本当でしょうか?
この中では、
5 + 4 - 3 を逆ポーランド記法に変換すると、5 4 3 - + なると書かれていますが、
それは間違っています。5 4 + 3 - になります。
5 + (4 - 3) なら、 5 4 3 - + になります。

アバター
usao
記事: 1566
登録日時: 6年前

Re: ファイルに書かれた式の計算について

#12

投稿記事 by usao » 3年前

この課題内容であれば,一見,
以下のような手続きでもやれそうに思ったのだけど,ダメなのかな.

コード:

●初期状態の準備

例えば
2 - 3 / 2 + 0.5 = 
なる式を読み込んだら
値と演算子に分けて以下のような配列に格納する
数[] = { 2, 3, 2, 0.5 }
演[] = { -, /, +, =   }


●演算処理

配列 演[] の中で一番優先度の高い演算子を探し,そのindexをiとする.
(この例では'/'が最も優先度が高く,i=1.)
↓
演[i]に応じた計算処理を行う.
 数[i] = (数[i] と 数[i+1] を用いた計算結果)  として,数[i]を計算結果で上書きし,
 不要になった数[i+1]と演[i]は配列から取り除く.
数[] = { 2, 1.5, 0.5 }
演[] = { -, +,   =   }

この手続きを,演算子が'='一つだけになるまで繰り返す→数[0]に最終的な答えが入っている.
この例だと,以下のようになる.

数[] = { 0.5, 0.5 }
演[] = { +,   =   }
↓
数[] = { 1 }
演[] = { = }

inemaru
記事: 108
登録日時: 3年前

Re: ファイルに書かれた式の計算について

#13

投稿記事 by inemaru » 3年前

課題の目的だけを達成するなら、
解析処理すら必要ない気がしたので書いてみました。

シェルに丸投げする方法です。
もちろん環境依存です。

Windows10・VS2013 環境で確認しました。
(パフォーマンスチューニングはしていません。)

コード:

#include <iostream>	// io
#include <cstdlib>	// system
#include <fstream>	// ifstream
#include <vector>	// vector
#include <string>	// string

// テキスト型
typedef std::vector<std::string> TextString_t;

// ファイルからテキスト読み込み
int LoadTextFromFile(const char* pFilePath, TextString_t& refText)
{
	std::ifstream ifs(pFilePath);
	if (ifs.fail()) {
		std::cerr << "読み込み失敗:" << pFilePath << std::endl;
		return -1;
	}
	std::string str;
	refText.clear();
	while (getline(ifs, str)) {
		refText.emplace_back(str);
	}
	return 0;
}

int main()
{
	// 文字取得
	TextString_t fileText;
	if (LoadTextFromFile("test.txt", fileText) != 0) {
		return -1;
	}

	// = を消去
	auto eraceEqualChar = [](std::string& refStr){
		for (size_t c = refStr.find_first_of('=');
			c != std::string::npos;
			c = refStr.find_first_of('=')) {
			refStr.erase(c, 1);
		}
	};

	// 一行ずつ処理
	for (auto& refStr : fileText){
		std::cout << refStr;
		eraceEqualChar(refStr);
		// PowerShell に丸投げ
		// (※この実装だと、計算毎にPowerShellが起動されるから超遅い)
		system(("powershell " + refStr).c_str());
	}

	// 入力待ち
	system("pause");
	return 0;
}

閉鎖

“C言語何でも質問掲示板” へ戻る