C言語での単語検索とカウントプログラム

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
SH

C言語での単語検索とカウントプログラム

#1

投稿記事 by SH » 7年前

C言語の勉強をしているものです。現在学校で「コマンドラインから複数の英単語を読み込み、同時にリダイレクト機能を使って標準入力から任意のテキストファイルを読み込みます。テキストファイルにコマンドラインで指定した英単語が何回出現したかを表示するプログラムをC言語で書きなさい。またgetopt関数は使わないこと。」という課題を出されました。しかし思った結果が出ず、困っております。以下が作成したプログラム、使用したテキストファイル、実行結果ですが、何が良くないのかすらわかっていない状態です。まずカウントされない理由を探るためにch = getchar();の直後にprintfで文字を出すようにしました。この時点では普通に内容が表示されますが、if(ch == ' ' || ch == '\n' || ch == EOF)の直後にprintfでchを出力すると何故かスペースになっていました(%dで32、%xで20でした)。他にもおかしな点がありますが分かる方いたら教えていただきたいです。今週の木曜日が期限で焦っています・・・。

[環境]
Mac El Captain 10.11.6
ターミナルにてemacsを使用
コンパイラはgcc

コード:

#include<stdio.h>
#include<string.h>

#define BUFF_MAX 128

int SearchAndCountWord(const char *search_wrd){
  char ch; //getcharでの入力をひとまず保持                                                                                                                                        
  char word_buff[BUFF_MAX] = {0}; //ここに文字列を格納して比較する                                                                                                                
  int  cnt = 0; //カウント用の変数、配列の何番目に文字を入れるかの決定                                                                                                        
  int  word_cnt = 0; //単語の出現回数を格納                                                                                                                                       

  printf("Search word is \"%s\"\n", search_wrd);

  while(1){ //EOF検知まで無限ループ                                                                                                                                               
    ch = getchar(); //テキストから一文字入力                                                                                                                                      
    printf("%c", ch); //ひとまず表示してみる
    if(ch == ' ' || ch == '\n' || ch == EOF){ //入力がスペース・改行・テキスト末端いずれかで                                                                                      
      if(word_buff[cnt - 1] == ',' || word_buff[cnt-1] == '.'){ //かつその前がコンマかピリオドなら                                                                                
        word_buff[cnt - 1] = '\0'; //nullにする                                                                                                                                     
      }
      if((strcmp(word_buff,search_wrd)) == 0){ //内容が一致なら                                                                                                                   
         word_cnt++; //カウントする                                                                                                                                               
         for(int j = 0; j < BUFF_MAX; j++) //バッファをnullで埋める(初期化)                                                                                                     
           word_buff[j] = '\0';
         cnt = 0; //文字の挿入位置を最初に戻す                                                                                                                                    
      }else{
        word_buff[cnt] = ch; //上の条件にヒットしなければ単語の途中なので文字を入れる                                                                                             
        cnt++; //次の配列番号に移動                                                                                                                                               
      }
    }
    if(ch == EOF){ //EOF検知でループ脱出                                                                                                                                          
      printf("[EOF]");
      break;
    }
  }
  return word_cnt;
}

int main(int argc, char **argv){
  int  word_cnt[argc - 1];

  for(int i = 1; i < argc; i++){
    word_cnt[i] = SearchAndCountWord(*(argv+i));
  }

  for(int i = 1; i < argc; i++)
    printf("\n%s : %d times\n", *(argv+i), word_cnt[i]);

  return 0;
}
読み込むテキストファイルにはオバマ大統領のスピーチ文のものが指定されていますが、長くテストし辛いので前段階として短いものを使っています。
[テキスト test.txt の内容]
char int int double float char void char double int void

[実行結果]
PC名:ディレクトリ名 ユーザー名$ ./test int void char < ./test.txt
Search word is "int"
char int int double float char void char double int void?[EOF]
int : 0 times
Search word is "void"
?[EOF]
void : 0 times
Search word is "char"
?[EOF]
char : 0 times

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

Re: C言語での単語検索とカウントプログラム

#2

投稿記事 by inemaru » 7年前

SH さんが書きました:他にもおかしな点がありますが分かる方いたら教えていただきたいです。
実行結果に直接関係ないですが、気になったので・・・

以下のコードは、おそらく環境依存です。
配列の宣言に変数を使用していますが、定数として解釈されている様です。
SH さんが書きました:

コード:

int  word_cnt[argc - 1];
Clang 3.8 / LLVM 3.8 では、コンパイルできましたが、
Visual Studio 2013 のコンパイラでは、コンパイルエラーします。

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

Re: C言語での単語検索とカウントプログラム

#3

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

SH さんが書きました:

コード:

int main(int argc, char **argv){
  int  word_cnt[argc - 1];

  for(int i = 1; i < argc; i++){
    word_cnt[i] = SearchAndCountWord(*(argv+i));
可変長配列はC99以降で使えるはずなので、VCで使えないのはVCが無能なだけでしょう。
しかし、どうしてわざわざ要素数を1減らしたのですか?
範囲外へのアクセス(読みor書き)は未定義動作になるので、十分な要素数確保しないといけません。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

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

Re: C言語での単語検索とカウントプログラム

#4

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

SH さんが書きました:

コード:

      if(word_buff[cnt - 1] == ',' || word_buff[cnt-1] == '.'){ //かつその前がコンマかピリオドなら                                                                                
        word_buff[cnt - 1] = '\0'; //nullにする                                                                                                                                     
      }
ここも、cnt == 0の時は範囲外の読みが発生し、未定義動作になるので、事前にチェックしないと怖いですね。

また、毎回SearchAndCountWord関数で標準入力を読み込もうとしても最初の1回読み込んだらその後は読み込めないことが考えられるので、
毎回読み込もうとするのではなく、単語を事前に読み込んで線形リストかなんかに保存し、保存したデータを用いて各クエリのカウントを行うといいでしょう。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

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

Re: C言語での単語検索とカウントプログラム

#5

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

SH さんが書きました:Search word is "int"
char int int double float char void char double int void?[EOF]
int : 0 times
限られた条件でしか単語を読み込んだバッファのNUL終端も初期化もしていないので、例えば2番目のintを読み込んだ後、charのrが残ったintrと比較されるので、カウントが上手くいかないようですね。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

C6b14

Re: C言語での単語検索とカウントプログラム

#6

投稿記事 by C6b14 » 7年前

もしかして、このように ”単語” を カウント したいのでは ? (emacs shell の内容がわからないので 推測)

コード:

#include<stdio.h>
#include<string.h>

#define BUFF_MAX 128

int SearchAndCountWord(const char *search_wrd) {

	char ch; //getcharでの入力をひとまず保持                                                                                                                                        
	char word_buff[BUFF_MAX] = { 0 }; //ここに文字列を格納して比較する                                                                                                                
	int  cnt = 0; //カウント用の変数、配列の何番目に文字を入れるかの決定                                                                                                        
	int  word_cnt = 0; //単語の出現回数を格納                                                                                                                                       

	printf("Search word is \"%s\"\n", search_wrd);

	while (1) { //EOF検知まで無限ループ                                                                                                                                               
		ch = getchar(); //テキストから一文字入力                                                                                                                                      
		printf("%c", ch); //ひとまず表示してみる

		if (ch == ' ' || ch == '\n' || ch == EOF) { //入力がスペース・改行・テキスト末端いずれかで                                                                                      
			if (word_buff[cnt - 1] == ',' || word_buff[cnt - 1] == '.') { //かつその前がコンマかピリオドなら                                                                                
				word_buff[cnt - 1] = '\0'; //nullにする                                                                                                                                     
			}
			if ((strcmp(word_buff, search_wrd)) == 0) { //内容が一致なら                                                                                                                   
				word_cnt++; //カウントする                                                                                                                                               
				for (int j = 0; j < BUFF_MAX; j++) //バッファをnullで埋める(初期化)                                                                                                     
					word_buff[j] = '\0';
				cnt = 0; //文字の挿入位置を最初に戻す                                                                                                                                    
			}
			else {
				word_buff[cnt] = ch; //上の条件にヒットしなければ単語の途中なので文字を入れる                                                                                             
				cnt++; //次の配列番号に移動                                                                                                                                               
			}
		}
		if (ch == EOF) { //EOF検知でループ脱出                                                                                                                                          
			printf("[EOF]");
			break;
		}
	}
	return word_cnt;
}

int main(int argc, char **argv) {

	printf("テスト文字列:char int int double float char void char double int void\n\n");
	/////
	printf("入力された文字:*argv=%s\n\n", *argv);
	int  word_cnt[12 - 1];
	for (int i = 1; i < argc; i++) {
		printf("入力された単語:*(argv + i)=%s\n", *(argv + i));
	}
	////
	for (int i = 1; i < argc; i++) {
		//word_cnt[i] = SearchAndCountWord(*(argv + i));
		word_cnt[i] = SearchAndCountWord("char int int double float char void char double int void");
	}

	for (int i = 1; i < argc; i++)
		printf("\n%s : %d times\n", *(argv + i), word_cnt[i]);

	return 0;
}
うごかしてもらえば 分かります。

C6b14

Re: C言語での単語検索とカウントプログラム

#7

投稿記事 by C6b14 » 7年前

あ ①を いれるのを 忘れてた ようです。アーギュメントの数ですよね。

コード:

int main(int argc, char **argv) {
	printf("①argc=%d\n", argc);

C6b14

Re: C言語での単語検索とカウントプログラム

#8

投稿記事 by C6b14 » 7年前

もちろん 

コード:

	for (int i = 1; i < argc; i++) {
		//word_cnt[i] = SearchAndCountWord(*(argv + i));
		word_cnt[i] = SearchAndCountWord("char int int double float char void char double int void");
	}
は 回す 意味は 無くなり ますし 基本的 考え方 の 話 です。

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

Re: C言語での単語検索とカウントプログラム

#9

投稿記事 by inemaru » 7年前

ミスがあるかもしれませんが
とりあえず、CとC++で動くものです。
追記:(以下のプログラム、英文を考慮していませんでした。ピリオドの対応してません。)
環境:VS2013/Windows10

コード:

// Cのつもり
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define BUFF_MAX 128

// 標準入力から単語を取得する
int GetWord(char* pText)
{
	for (int idx = 0; idx < BUFF_MAX; ++idx) {
		char ch = getchar();
		// 単語の区切り
		if (ch == ' ' || ch == '\n') {
			break;
		}
		// 終端
		if (ch == EOF) {
			return EOF;
		}
		pText[idx] = ch;
	}
	return 0;
}

// コマンドライン引数の英単語と一致するか確認する
void CountWord(int* pWordCountArray, char* countText, int argc, char** argv)
{
	for (int idx = 1; idx < argc; ++idx) {
		if (strcmp(countText, argv[idx]) == NULL) {
			++(pWordCountArray[idx]);
		}
	}
}

int main(int argc, char** argv)
{
	// 変数
	// ↓ VC環境以外では、動的確保でなくても大丈夫の様です
	int*	word_cnt = (int*)calloc(argc, sizeof(int));
	char	text[BUFF_MAX] = "";
	int		getWord_Result = GetWord(text);

	// カウント処理
	while (getWord_Result != EOF) {
		CountWord(word_cnt, text, argc, argv);
		for (int idx = 0; idx < BUFF_MAX; ++idx) {
			text[idx] = 0;
		}
		getWord_Result = GetWord(text);
	}
	CountWord(word_cnt, text, argc, argv);

	// 結果表示
	for (int idx = 1; idx < argc; ++idx) {
		printf("\n%s : %d times\n", *(argv + idx), word_cnt[idx]);
	}

	free(word_cnt);
	return 0;
}

コード:

// C++で書けば楽だけど、C言語指定・・・
#include <iostream>		// 標準入出力
#include <vector>		// std::vector
#include <string>		// std::string
#include <map>			// std::map
#include <sstream>		// std::stringstream

int main(int argc, char **argv)
{
	// コマンドライン引数を取得する
	std::vector<std::string> args(argv, argv + argc);

	// 標準入力から単語を取り出す
	std::string lineStr;
	std::vector<std::string> elms;
	// 標準入力から一行ずつ取得
	while (std::getline(std::cin, lineStr)) {
		// 取得した文字列を' 'を使用して単語に分割する
		std::string inputStr;
		std::stringstream ss(lineStr);
		while (std::getline(ss, inputStr, ' ')) {
			elms.emplace_back(inputStr);
		}
	}

	// 単語をキーにしてカウントする
	std::map<std::string, int> map;
	for (const std::string& str : elms) {
		auto it = map.find(str);
		if (it == map.end()) {
			map[str] = 1;
		}
		else {
			++(it->second);
		}
	}

	// コマンドライン引数で受け取った文字列のみ個数を表示
	for (unsigned int idx = 1; idx < args.size(); ++idx) {
		std::cout << args[idx] << " : " << map[args[idx]] << " times" << std::endl;
	}

	return 0;
}
オフトピック
みけCAT さんが書きました:
SH さんが書きました:

コード:

int main(int argc, char **argv){
  int  word_cnt[argc - 1];

  for(int i = 1; i < argc; i++){
    word_cnt[i] = SearchAndCountWord(*(argv+i));
可変長配列はC99以降で使えるはずなので、VCで使えないのはVCが無能なだけでしょう。
しかし、どうしてわざわざ要素数を1減らしたのですか?
範囲外へのアクセス(読みor書き)は未定義動作になるので、十分な要素数確保しないといけません。
C99以降のC言語で配列を宣言する構文は、
<型> <変数名>[<要素数(定数)>];
となっていませんでしたっけ?
コマンドライン引数は、変数ですが
最適化で定数になっているので宣言ができているように見えます。
最後に編集したユーザー inemaru on 2016年11月27日(日) 11:29 [ 編集 3 回目 ]

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

Re: C言語での単語検索とカウントプログラム

#10

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

inemaru さんが書きました:C99以降のC言語で配列を宣言する構文は、
<型> <変数名>[<要素数(定数)>];
となっていませんでしたっけ?
なっていません。
C99(N1256 6.7.5.2 Array declarators)では
3 If, in the declaration ‘‘T D1’’, D1 has one of the forms:
D[ type-qualifier-list opt assignment-expression opt ]
D[ static type-qualifier-list opt assignment-expression ]
D[ type-qualifier-list static assignment-expression ]
D[ type-qualifier-list opt * ]
and the type specified for ident in the declaration ‘‘T D’’ is ‘‘derived-declarator-type-list
T’’, then the type specified for ident is ‘‘derived-declarator-type-list array of T’’. 123)
(See 6.7.5.3 for the meaning of the optional type qualifiers and the keyword static.)
5 If the size is an expression that is not an integer constant expression: if it occurs in a
declaration at function prototype scope, it is treated as if it were replaced by *; otherwise,
each time it is evaluated it shall have a value greater than zero. The size of each instance
of a variable length array type does not change during its lifetime. Where a size
expression is part of the operand of a sizeof operator and changing the value of the
size expression would not affect the result of the operator, it is unspecified whether or not
the size expression is evaluated.
C11(N1570 6.7.6.2 Array declarators)では
3 If, in the declaration ‘‘T D1’’, D1 has one of the forms:
D[ type-qualifier-list opt assignment-expression opt ]
D[ static type-qualifier-list opt assignment-expression ]
D[ type-qualifier-list static assignment-expression ]
D[ type-qualifier-list opt * ]
and the type specified for ident in the declaration ‘‘T D’’ is ‘‘derived-declarator-type-list
T’’, then the type specified for ident is ‘‘derived-declarator-type-list array of T’’. 142)
(See 6.7.6.3 for the meaning of the optional type qualifiers and the keyword static.)
5 If the size is an expression that is not an integer constant expression: if it occurs in a
declaration at function prototype scope, it is treated as if it were replaced by *; otherwise,
each time it is evaluated it shall have a value greater than zero. The size of each instance
of a variable length array type does not change during its lifetime. Where a size
expression is part of the operand of a sizeof operator and changing the value of the
size expression would not affect the result of the operator, it is unspecified whether or not
the size expression is evaluated.
となっており、assignment-expressionとは一番「外側」(ASTで一番根に近い所)の演算子がコンマ演算子でない式のことなので、
いずれも定数でない式が要素数として使えることがわかります。

ついでに、inemaruさんが<変数名>と書いている所も、変数名だけでなくポインタや配列などの宣言が入ることが考えられます。
inemaru さんが書きました:コマンドライン引数は、変数ですが
最適化で定数になっているので宣言ができているように見えます。
コマンドライン引数の数は、実行時にならないとわかりません。
それを最適化で定数にしてしまうのであれば、それはコンパイラのバグでしょう。
最後に編集したユーザー みけCAT on 2016年11月27日(日) 10:22 [ 編集 1 回目 ]
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

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

Re: C言語での単語検索とカウントプログラム

#11

投稿記事 by inemaru » 7年前

オフトピック
みけCAT さんが書きました: となっており、assignment-expressionとは一番「外側」(ASTで一番根に近い所)の演算子がコンマ演算子でない式のことなので、
いずれも定数でない式が要素数として使えることがわかります。
コマンドライン引数の数は、実行時にならないとわかりません。
それを最適化で定数にしてしまうようなコンパイラは欠陥品でしょう。
解説ありがとうございます。納得しました。
単純にVCが対応していないだけなんですね。
勉強になりました。

あんどーなつ
記事: 171
登録日時: 7年前
連絡を取る:

Re: C言語での単語検索とカウントプログラム

#12

投稿記事 by あんどーなつ » 7年前

読みやすいバージョンを書いてみました。
このプログラムは空白やピリオドごとに文章を分割しているわけではないので、
単語にgo, 文章内にgogo!をいれるとgoが2回ヒットします。

コード:

#include <stdio.h>
#include <string.h>

#define MAXCNT 20
#define MAXBUF 128

// str1内のstr2の数をカウント
int strcnt(char *str1, char *str2) {
	int cnt = 0;
	while ((str1 = strstr(str1, str2)) != NULL) {
		cnt++;
		str1 += strlen(str2);
	}
	return cnt;
}

int main(int argc, char **argv)
{
	int cnts = ((argc-1) <= MAXCNT) ? (argc-1) : MAXCNT;
	int cnt[MAXCNT] = {}; // all 0
	char **word = argv + 1; // プログラム名を除外
	char line[MAXBUF+1];
	int i;
	while (fgets(line, MAXBUF, stdin) != NULL) {
		for (i = 0; i < cnts; i++)
			cnt[i] += strcnt(line, word[i]);
	}
	for (i = 0; i < cnts; i++)
		printf("  %10s : %d times matched.\n", word[i], cnt[i]);
    return 0;
}

かずま

Re: C言語での単語検索とカウントプログラム

#13

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

SH さんが書きました:この時点では普通に内容が表示されますが、if(ch == ' ' || ch == '\n' || ch == EOF)の直後にprintfでchを出力すると何故かスペースになっていました(%dで32、%xで20でした)。他にもおかしな点がありますが分かる方いたら教えていただきたいです。
word_buff[cnt] = ch; が単語の終わりの時だけ実行されているからでしょう。
else は、if((strcmp ではなく、if(ch == ' ' に対応させなければなりません。

他におかしいところは、次のところ。

最初の単語 argv[1] について入力ファイルを EOF まで全部読み切ってしまったら
次の単語 argv[2] のためにファイルを読み込めません。
rewind(stdin); で読み込み位置をファイルの先頭に戻しましょう。

word_buff と cnt の再初期化は、内容の一致にかかわらず、単語の終わりでは
必ず実行しなければならない。

修正するとすれば、

コード:

#include<stdio.h>
#include<string.h>

#define BUFF_MAX 128

int SearchAndCountWord(const char *search_wrd){
  char ch; //getcharでの入力をひとまず保持
  char word_buff[BUFF_MAX] = {0}; //ここに文字列を格納して比較する
  int  cnt = 0; //カウント用の変数、配列の何番目に文字を入れるかの決定
  int  word_cnt = 0; //単語の出現回数を格納

  rewind(stdin);
  printf("Search word is \"%s\"\n", search_wrd);

  while(1){ //EOF検知まで無限ループ
    ch = getchar(); //テキストから一文字入力
    printf("%c", ch); //ひとまず表示してみる
    if(ch == ' ' || ch == '\n' || ch == EOF){ //入力がスペース・改行・テキスト末端いずれかで
      if(cnt > 0 && (word_buff[cnt - 1] == ',' || word_buff[cnt-1] == '.')){ //かつその前がコンマかピリオドなら
        word_buff[cnt - 1] = '\0'; //nullにする
      }
      if((strcmp(word_buff,search_wrd)) == 0){ //内容が一致なら
         word_cnt++; //カウントする
      }
      for(int j = 0; j < BUFF_MAX; j++) //バッファをnullで埋める(初期化)
         word_buff[j] = '\0';
      cnt = 0; //文字の挿入位置を最初に戻す
      if(ch == EOF){ //EOF検知でループ脱出
        printf("[EOF]");
        break;
      }
    }
    else{
        word_buff[cnt] = ch; //上の条件にヒットしなければ単語の途中なので文字を入れる
        cnt++; //次の配列番号に移動
    }
  }
  return word_cnt;
}

int main(int argc, char **argv){
  int  word_cnt[100];

  for(int i = 1; i < argc; i++){
    word_cnt[i] = SearchAndCountWord(*(argv+i));
  }

  for(int i = 1; i < argc; i++)
    printf("\n%s : %d times\n", *(argv+i), word_cnt[i]);
  return 0;
}
入力テキストに ? や ! があったらどうしますか?
単語を英字のみからなるものとすると、次のように書けます。

コード:

#include <stdio.h>   // scanf, printf
#include <stdlib.h>  // calloc, free
#include <string.h>  // strcmp
 
int main(int argc, char *argv[])
{
    char word[100];
    int i, *count = calloc(argc, sizeof(int));
    while (scanf("%*[^a-zA-Z]"), scanf("%99[a-zA-Z]", word) == 1)
        for (i = 1; i < argc; i++)
            if (strcmp(word, argv[i]) == 0) count[i]++;
    for (i = 1; i < argc; i++)
        printf("%s: %d times\n", argv[i], count[i]);
    free(count);
    return 0;
}

SH

Re: C言語での単語検索とカウントプログラム

#14

投稿記事 by SH » 7年前

多くの返信ありがとうございます。ひとまず分かる限りで手直ししたのが以下のものです・・・。指摘された通り未定義動作の対策や、nullの入れる条件がおかしいようでした。以下のプログラムだと、一つ目のワードはカウントできますが、それ以降のものは出来ません。また、線形リストやクエリといったものは学習しておらずよく分かりません・・・。

コード:

#include<stdio.h>
#include<string.h>

#define BUFF_MAX 128

int SearchAndCountWord(char *search_wrd){
  char ch; //getcharでの入力をひとまず保持                                                         
  char buff[BUFF_MAX] = {0}; //ここに文字列を格納して比較する                                      
  int  cnt = 0; //カウント用の汎用変数、配列の何番目に文字を入れるかの決定                         
  int  word_cnt = 0; //単語の出現回数を格納                                                        

  while(1){ //EOF検知まで無限ループ                                                                
    ch = getchar(); //テキストから一文字入力                                                       
    if(ch == ' ' || ch == '\n' || ch == EOF){ //入力がスペース・改行・テキスト末端いずれかで       
      if(0 < cnt){
        if(buff[cnt - 1] == ',' || buff[cnt - 1] == '.') //その前がコンマかピリオドなら            
          buff[cnt - 1] = '\0'; //null挿入(コンマ・ピリオド消去)                                 
      }
      buff[cnt] = '\0'; //終端設定                                                                 
      if((strcmp(buff,search_wrd)) == 0) //内容が一致なら                                          
         word_cnt++; //カウントする                                                                
      for(int i = 0; i < BUFF_MAX; i++) //バッファをnullで埋める(初期化)                         
        buff[i] = '\0';
      cnt = 0; //文字の挿入位置を最初に戻す                                                        
    }else{
      buff[cnt] = ch; //上の条件にヒットしなければ単語の途中なので文字を入れる                     
      cnt++; //次の配列番号に移動                                                                  
    }

    if(ch == EOF){ //EOF検知でループ脱出                                                           
      break;
    }
  }

  return word_cnt; //カウント数を返す                                                              
}

int main(int argc, char **argv){
  int  word_cnt;

  for(int i = 1; i < argc; i++){
    word_cnt = SearchAndCountWord(*(argv+i));
    printf("%s : %d times\n", *(argv+i), word_cnt);
  }

  return 0;
}
[テキスト内容]
char int int init float double double char.

[実行結果]
PC名:ディレクトリ名 ユーザー名$ ./test int char double < ./test.txt
int : 2 times
char : 0 times
double : 0 times

かずま

Re: C言語での単語検索とカウントプログラム

#15

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

SH さんが書きました:以下のプログラムだと、一つ目のワードはカウントできますが、それ以降のものは出来ません。
すでに指摘していますよ。
かずま さんが書きました:最初の単語 argv[1] について入力ファイルを EOF まで全部読み切ってしまったら
次の単語 argv[2] のためにファイルを読み込めません。
rewind(stdin); で読み込み位置をファイルの先頭に戻しましょう。

C6b14

Re: C言語での単語検索とカウントプログラム

#16

投稿記事 by C6b14 » 7年前

すみません。いまの 状態で argc **argv には どんな 値が入ります。( emacs はよく知らないので )

あんどーなつ
記事: 171
登録日時: 7年前
連絡を取る:

Re: C言語での単語検索とカウントプログラム

#17

投稿記事 by あんどーなつ » 7年前

かずま さんが書きました: rewind(stdin); で読み込み位置をファイルの先頭に戻しましょう。
すみません。パイプの場合は元に戻せないようです。

コード:

#include <stdio.h>

int main() {
        char str[257];
        fgets(str, 256, stdin);
        printf("%s", str);
        rewind(stdin);
    fgets(str, 256, stdin);
    printf("%s", str);
        return 0;
}

コード:

$ printf "ABC\nABC"
ABC
ABC
$ printf "ABC\nABC" | ./a.exe
ABC
ABC

あんどーなつ
記事: 171
登録日時: 7年前
連絡を取る:

Re: C言語での単語検索とカウントプログラム

#18

投稿記事 by あんどーなつ » 7年前

プログラムを修正して、単語ごとにカウントできるようにしました。

コード:

#include <stdio.h>
#include <string.h>
#include <ctype.h>
 
#define MAXCNT 20
#define MAXBUF 128

// line内のwordの数をカウント
int wdcnt(char *line, char *word) {
	char line2[MAXBUF];
	int len = strlen(line);
	int i;
	int cnt = 0;
	strcpy(line2, line);
	for (i = 0; i < len; i++)
		if (!isalpha(line2[i]))
			line2[i] = '\0';
	for (i = 0; i < len; i++) {
		if (line2[i] == '\0')
			continue;
		if (strcmp(line2 + i, word) == 0)
			cnt++;
		i += strlen(line2 + i);
	}
    return cnt;
}
 
int main(int argc, char **argv)
{
    int cnts = ((argc-1) <= MAXCNT) ? (argc-1) : MAXCNT;
    int cnt[MAXCNT] = {}; // all 0
    char **word = argv + 1; // プログラム名を除外
    char line[MAXBUF+1];
    int i;
    while (fgets(line, MAXBUF, stdin) != NULL) {
        for (i = 0; i < cnts; i++)
            cnt[i] += wdcnt(line, word[i]);
    }
    for (i = 0; i < cnts; i++)
        printf("  %10s : %d times matched.\n", word[i], cnt[i]);
    return 0;
}

コード:

$ printf "whatever, what?\nwhatever!"
whatever, what?
whatever!
$ printf "whatever, what?\nwhatever!" | ./a.exe what whatever
        what : 1 times matched.
    whatever : 2 times matched.

あんどーなつ
記事: 171
登録日時: 7年前
連絡を取る:

Re: C言語での単語検索とカウントプログラム

#19

投稿記事 by あんどーなつ » 7年前

あんどーなつ さんが書きました:
かずま さんが書きました: rewind(stdin); で読み込み位置をファイルの先頭に戻しましょう。
すみません。パイプの場合は元に戻せないようです。
間違えました。もう一回試験した結果です。

コード:

#include <stdio.h>

int main() {
    char str[257];
    fgets(str, 256, stdin);
    printf("%s", str);
    fgets(str, 256, stdin);
    printf("%s", str);
    rewind(stdin);
    fgets(str, 256, stdin);
    printf("%s", str);
    fgets(str, 256, stdin);
    printf("%s", str);
    return 0;
}

コード:

$ printf "ABC\nABC"
ABC
ABC
$ printf "ABC\nABC" | ./a.exe
ABC
ABCABCABC
ABCは4回出力されているみたいですが、rewind後は改行がなくなっているようです。

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

Re: C言語での単語検索とカウントプログラム

#20

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

あんどーなつ さんが書きました:ABCは4回出力されているみたいですが、rewind後は改行がなくなっているようです。
rewindが効かず、2回目のfgetsで読み込まれた改行無しのABCが3回目および4回目のfgetsでは更新されずそのまま出力されていると予想できます。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

あんどーなつ
記事: 171
登録日時: 7年前
連絡を取る:

Re: C言語での単語検索とカウントプログラム

#21

投稿記事 by あんどーなつ » 7年前

かずま さん

すみません。どうしても気になったもので。
大きいデータを入れて確認しました。

コード:

#include <stdio.h>

int main() {
    char str[257];
    int i;
    for (i = 0; i < 99998; i++) {
        fgets(str, 256, stdin);
        printf("%05s", str);
    }
    rewind(stdin);
    for (i = 0; i < 99998; i++) {
        fgets(str, 256, stdin);
        printf("%05s", str);
    }
    return 0;
}

コード:

$ perl -e 'for ($i=0; $i<=99999;$i++) {printf("%05d\n", $i); }' > test.out

$ perl -e 'for ($i=0; $i<=99999;$i++) {printf("%05d\n", $i); }' | ./a.exe > test2.out

$ tail -10 test.out
99990
99991
99992
99993
99994
99995
99996
99997
99998
99999

$ tail -10 test2.out
99999
99999
99999
99999
99999
99999
99999
99999
99999
99999

SH

Re: C言語での単語検索とカウントプログラム

#22

投稿記事 by SH » 7年前

かずま さんが書きました:
SH さんが書きました:以下のプログラムだと、一つ目のワードはカウントできますが、それ以降のものは出来ません。
すでに指摘していますよ。
かずま さんが書きました:最初の単語 argv[1] について入力ファイルを EOF まで全部読み切ってしまったら
次の単語 argv[2] のためにファイルを読み込めません。
rewind(stdin); で読み込み位置をファイルの先頭に戻しましょう。
えっと・・・rewind関数自体初めて聞きました。一回目のSearchAndCountWord()でファイル終端まで行くと二回目に呼び出してgetchar()をしたとしても終端なので何も文字が読み込まれず、それを比較しても一致はせずカウントされないということでしょうか?

ここまで多数の返信をいただきながとても重要なことを書いていませんでした。講師は課題には学習した範囲までの内容しか使ってはいけないという人で、具体的に言うと、授業で使用される「新・明解C言語(柴田望洋)」の11章までの内容で作成しなければなりません。よって、rewind()
やfgetなどのファイル系の関数、構造体などは使用できません。(以前まだ習っていないstrcmpを使用し評価を下げられました。現在は学習済みです。)
大変申し訳ありません・・・。

プログラムについてですが、私のプログラムに手を加えることで以下の理想の動きをさせることは可能なのでしょうか?それとも根本的に作り直すべきなのでしょうか?曖昧な質問になってしまっているかもしれませんがどうぞよろしくお願いします・・・。

[理想の動作例]
以下の内容のテキストファイル、test.textがあったとする。
Seventy-one years ago, on a bright cloudless morning, death fell from the sky and the world was changed. A flash of light and a wall of fire destroyed a city and demonstrated that mankind possessed the means to destroy itself.

PC名:ディレクトリ名 ユーザー名$ ./test ago and wall car < ./test.txt
ago : 1 times
and : 3 times
wall : 1 times
car : 0 times

C6b14

Re: C言語での単語検索とカウントプログラム

#23

投稿記事 by C6b14 » 7年前

普通に 考えると emacs の shell スクリプト がいるような 気がしますが。

C6b14

Re: C言語での単語検索とカウントプログラム

#24

投稿記事 by C6b14 » 7年前

線形リスト も riwind も できなければ ぱ パイプ した データ  の 再使用 が 出来ないような。だから 1回しか 出来ない と 思う だけで す。 よく わからないので 思っただけです。 Windows 系しかないので おかしなことなら ごめんなさい。

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

Re: C言語での単語検索とカウントプログラム

#25

投稿記事 by inemaru » 7年前

SH さんが書きました: ここまで多数の返信をいただきながとても重要なことを書いていませんでした。講師は課題には学習した範囲までの内容しか使ってはいけないという人で、具体的に言うと、授業で使用される「新・明解C言語(柴田望洋)」の11章までの内容で作成しなければなりません。よって、rewind()
やfgetなどのファイル系の関数、構造体などは使用できません。(以前まだ習っていないstrcmpを使用し評価を下げられました。現在は学習済みです。)
大変申し訳ありません・・・。
具体的に何を使用して良いかわからなかったので
授業で使用している書籍を調べましたが、該当書籍が3種類ありました。

新・明解C言語入門編 目次
http://www.bohyoh.com/Books/NewMeikaiC/contents.html
新・明解C言語中級編 目次(11章が無いので違う?)
http://www.bohyoh.com/Books/NewMeikaiC02/contents.html

新・明解C言語実践編 目次
http://www.bohyoh.com/Books/NewMeikaiC03/contents.html

SH

Re: C言語での単語検索とカウントプログラム

#26

投稿記事 by SH » 7年前

またしても情報不足ですみません・・・使用しているのは3つの内の入門編です。

あんどーなつ
記事: 171
登録日時: 7年前
連絡を取る:

Re: C言語での単語検索とカウントプログラム

#27

投稿記事 by あんどーなつ » 7年前

ごめんなさい。プログラムは全書き直ししています。
ただ、変数名、コメントはできるだけ似せるようにしました。

インデントは2tabでなくて4tabでもよかったでしょうか?
また、isalpha, strcmp, strlen関数を使っています。まずいものはありますか?
まずければ直しますので、よろしくお願いいたします。

コード:

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

#define BUFF_MAX 10000
#define WORD_MAX 100

char buff[BUFF_MAX+1]; // '\0'の分多く確保

int main(int argc, char *argv[]) {
	int ch; // getcharでの入力をひとまず保持
	int buff_size = 0; // バッファのサイズ
	int word_cnt[WORD_MAX] = {0}; // 単語の出現回数を格納
	int i, j; // カウント用の汎用変数、配列の何番目に文字を入れるかの決定

	// 標準入力をバッファに格納
	while (i < BUFF_MAX && (ch = getchar()) != EOF) {
		buff[i] = ch;
		i++;
	}
	buff[i] = '\0';
	buff_size = i;

	// アルファベット以外'\0'に置き換える
	for (i = 0; i < buff_size; i++)
		if (!isalpha(buff[i]))
			buff[i] = '\0';

	// バッファ内の単語をサーチ
	for (i = 0; i < buff_size; i++) {
		while (buff[i] == '\0') { // ヌル文字なら飛ばす
			i++;
			continue;
		}
		// コマンドラインの全単語について比較
		for (j = 1; j < argc; j++) {
			if (j >= WORD_MAX) break; // ただし配列の大きさの上限でやめる
			if (strcmp(buff + i, argv[j]) == 0)
				word_cnt[j]++;
		}
		i += strlen(buff + i); // 単語の最後まで飛ばす
	}

	// 結果の表示
	for (j = 1; j < argc; j++) {
		printf("%s : %d times.\n", argv[j], word_cnt[j]);
	}

	return 0;
}

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

Re: C言語での単語検索とカウントプログラム

#28

投稿記事 by inemaru » 7年前

SH 様の提示されたコードを修正した場合
自分は、rewind()を使用しない方法がわからないので、

前に書いたコードを英文対応させてみました。
SH様のコードで使用されている関数を使用しているので
関数縛りは大丈夫かなと微修正

あまり、綺麗に書けていませんが参考になれば幸いです。
(ミスがあるかもしれませんので、ご注意ください)
SH さんが書きました: [理想の動作例]
以下の内容のテキストファイル、test.textがあったとする。
Seventy-one years ago, on a bright cloudless morning, death fell from the sky and the world was changed. A flash of light and a wall of fire destroyed a city and demonstrated that mankind possessed the means to destroy itself.

PC名:ディレクトリ名 ユーザー名$ ./test ago and wall car < ./test.txt
ago : 1 times
and : 3 times
wall : 1 times
car : 0 times
必要に応じて、
14行目付近の「単語の区切り」の部分に条件を追加すれば、!や?にも対応できます。

注意点:
書籍の目次で確認できなかった関数は、使用していません。
本来、もっとシンプルに記述できる部分があります。

コード:

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

#define BUFF_MAX 128

// 標準入力から単語を取得する
int GetWord(char* pText)
{
	int idx = 0;
	for (; idx < BUFF_MAX; ++idx) {
		char ch = getchar();
		// 単語の区切り
		if (ch == ' ' || ch == '\n' || ch == '.' || ch == ',') {
			break;
		}
		// 終端
		if (ch == EOF) {
			return EOF;
		}
		pText[idx] = ch;
	}
	return 0;
}

// コマンドライン引数の英単語と一致するか確認してカウント
void CountWord(int* pWordCountArray, char* countText, int argc, char** argv)
{
	int idx = 1;
	for (; idx < argc; ++idx) {
		if (strcmp(countText, argv[idx]) == NULL) {
			++(pWordCountArray[idx]);
		}
	}
}

int main(int argc, char** argv)
{
	// コマンドライン引数に単語が無ければ処理しない
	if (argc == 1) {
		return 0;
	}

	// 変数
	int		word_cnt[argc] = {0};
	char	text[BUFF_MAX] = "";
	int		getWord_Result = GetWord(text);
	int		idx = 0;

	// カウント処理
	while (getWord_Result != EOF) {
		CountWord(word_cnt, text, argc, argv);
		for (idx = 0; idx < BUFF_MAX; ++idx) {
			text[idx] = 0;
		}
		getWord_Result = GetWord(text);
	}
	if (*text != 0){
		CountWord(word_cnt, text, argc, argv);
	}
	
	// 結果表示
	for (idx = 1; idx < argc; ++idx) {
		printf("\n%s : %d times\n", *(argv + idx), word_cnt[idx]);
	}
	return 0;
}

SH

Re: C言語での単語検索とカウントプログラム

#29

投稿記事 by SH » 7年前

いただいた指摘などを参考に、何とか完成させることが出来ました・・・。皆様、ありがとうございました。返信も早く大変助かりました。

かずま

Re: C言語での単語検索とカウントプログラム

#30

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

SH さんが書きました:いただいた指摘などを参考に、何とか完成させることが出来ました・・・。
完成したプログラムを提示してください。

isalpha は使っていいのですか?

コード:

#include <stdio.h>   // getchar, printf
#include <ctype.h>   // isalpha
#include <string.h>  // strcmp
 
int getword(char *word, int n)
{
    int ch, i;
    while ((ch = getchar()) != EOF)
        if (isalpha(ch)) break;
    n--;
    for (i = 0; i < n && isalpha(ch); i++) {
        word[i] = ch;
        ch = getchar();
    }
    word[i] = '\0';
    return i;
}

int main(int argc, char *argv[])
{
    char word[100];
    int i, count[128] = { 0 };
    if (argc > 128) argc = 128;
    while (getword(word, sizeof word))
        for (i = 1; i < argc; i++)
            if (strcmp(word, argv[i]) == 0) count[i]++;
    for (i = 1; i < argc; i++)
        printf("%s: %d times\n", argv[i], count[i]);
    return 0;
}

SH

Re: C言語での単語検索とカウントプログラム

#31

投稿記事 by SH » 7年前

こちらになります・・・。関数化の利点を放り投げたような状態ですがひとまず理想の動きは致します・・・。現状考えつく限界でした。EOFまで移動した場合何もしないとそのままの位置なのは初めて知りました。位置をファイルの最初に戻す方法が分からない、分かったとしても未学習の領域なため、一度のEOFへの移動で全ての単語をカウントしてしまおうとした結果こうなりました・・・。

コード:

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

#define BUFF_MAX 256

void SearchCountAndShowWord(int word_num, char *words[]){
  char ch; //getcharでの入力をひとまず保持                                                                                                                                        
  char buff[BUFF_MAX] = {0}; //ここに文字列を格納して比較する                                                                                                                     
  int  cnt = 0; //カウント用の汎用変数、配列の何番目に文字を入れるかの決定                                                                                                        
  int  word_cnt[word_num]; //単語の出現回数を格納                                                                                                                                 

  for(int i = 0; i < word_num; i++)
    word_cnt[i] = 0;

  while(1){ //EOF検知まで無限ループ                                                                                                                                               
    ch = getchar(); //テキストから一文字入力                                                                                                                                      
    if(ch == ' ' || ch == '\n' || ch == EOF){ //入力がスペース・改行・テキスト末端いずれかで                                                                                      
      if(0 < cnt){
        if(isalnum(buff[cnt - 1]) == 0) //その前が英数字でなければ                                                                                                                
          buff[cnt - 1] = '\0'; //null挿入(コンマ・ピリオドなどを消去)                                                                                                                
      }
      buff[cnt] = '\0'; //終端設定                                                                                                                                                
      for(int i = 1; i < word_num; i++){
        if((strcmp(buff,words[i])) == 0)
          word_cnt[i]++;
      }
      for(int j = 0; j < BUFF_MAX; j++) //バッファをnullで埋める(初期化)                                                                                                        
        buff[j] = '\0';
      cnt = 0; //文字の挿入位置を最初に戻す                                                                                                                                       
    }else{
      buff[cnt] = ch; //上の条件にヒットしなければ単語の途中なので文字を入れる                                                                                                    
      cnt++; //次の配列番号に移動                                                                                                                                                 
    }

    if(ch == EOF){ //EOF検知でループ脱出                                                                                                                                          
      break;
    }
  }
  for(int k = 1; k < word_num; k++) //結果表示                                                                                                                                    
    printf("%s : %d times\n", words[k], word_cnt[k]);

  return; //終了                                                                                                                                                                  
}

int main(int argc, char **argv){

  SearchCountAndShowWord(argc,argv);

  return 0;
}

閉鎖

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