- ベストアンサー
c言語で文書を読み込み、単語の出現頻度を教える
- c言語の課題で、与えられた文書を読み込んでその中にある単語の出現頻度を教えるプログラミングを作成しているのですが、うまくいきません。
- プログラムを実行すると、一部の単語が正しくカウントされず、結果が異なってしまいます。
- どこが間違っているのでしょうか?正しい出現頻度を求める方法を教えてください。
- みんなの回答 (4)
- 専門家の回答
質問者が選んだベストアンサー
まずは、「設計図」をきちんと作りましょう。 ・合計を表示するには、集計が全て終了してからでなければなりません。 途中で出力できるのは、途中経過だけです。 よって、プログラムは 単語毎に数を数える ↓ 単語毎の数と単語数を出力する という(最低でも)2部構成になっている必要があります。 このプログラムは、一単語毎に、その単語の表示をしています。 ・集計のロジックが間違っています。 もし 既に登録されている単語だったら その単語の count++ そうでなかたら 新規に単語を登録 count=1 以上を、ファイル全体で繰り返す 集計部分のロジックはこうなると思います。 御自身のプログラムを、このように日本語に置き換えてみたら、期待通りに集計してくれそうですか? 紙と鉛筆を用意して、御自身がコンピュータになったつもりで、このプログラムの通りに「実行」してみるのもよいでしょう。 ・ポインタと文字列の違いを理解しましょう。 C言語でもっとも難しいところではあるのですが s = strtok(buffer,delimiter); t[numword].token=s; 他の言語だと、t[numword].token で記録する文字列が s と同じ内容になる、という場合があります。 しかし、このプログラムは 「t[numword].token で記録するポインタ(アドレス)が、 s のポインタの値と同じになる」ものです。 具体的に言うと。 char buffer[1024]; これで、配列 buffer が用意されました。このとき、100番地から確保されたとします。 bufferに1行読み込むと 100番地: A 101番地: s 102番地: 103番地: s 104番地: w 105番地: e ... という状態になります。 s = strtok(buffer,delimiter); で、トークンが見付かった場合、sはその先頭アドレスを返します。この例ならば、As の先頭の100番地です。 t[numword].token=s; とすると、 t[numword].tokenには「100番地」が入ります。 「Asという文字列」ではありません。 ここで、問題になるんは、次の行をbufferに読み込んだときです。 bufferに1行読み込むと 100番地: c 101番地: o 102番地: a 103番地: t 104番地: , ... となります。 すると、さっき「As」を覚えさせたつもりが、100番地からの文字列が「As」ではなくなってしまっています。 ずっと覚えさせておくには tokenを 配列にして、strcpyで記録する tokenを ポインタのまま、mallocで領域を確保して、strcpyで記録する tokenを ポインタのまま、bufferではなく毎回の領域に読み込むようにする 等の工夫が必要です。
その他の回答 (3)
- maiko0318
- ベストアンサー率21% (1483/6969)
私の指摘では足りませんでした。(間違いもm(__)m) で、テストして動くソースを載せておきます。 #include<stdio.h> #include<stdlib.h> #include<string.h> #include<ctype.h> typedef struct token_checker{ char *token; int count; }TOKEN_CHECKER; int main(int argc,char* argv[]){ FILE * fp; char buffer[1024]; int numword=0; char * delimiter = " .,"; char * s; TOKEN_CHECKER *t; int i; int find; t=(TOKEN_CHECKER*)malloc(sizeof(TOKEN_CHECKER)*24); if(argc != 2){ printf("Parameter error.\n"); exit(1); } if((fp=fopen(argv[1],"r"))==NULL){ printf("File open error.\n"); exit(1); } while( fgets(buffer,sizeof(buffer),fp)!=NULL){ buffer[ strlen(buffer)-1]='\0'; s = strtok(buffer,delimiter); if(s != NULL){ find=0; for(i=1;i<=numword;i++){ if(strcmp(s,t[i].token)==0){ t[i].count++; find=1; } } if(find==0){ numword++; t[numword].token=(char*)malloc(strlen(s)+1); strcpy(t[numword].token,s); t[numword].count=1; } } while((s=strtok(NULL,delimiter)) != NULL){ find=0; for(i=1;i<=numword;i++){ if(strcmp(s,t[i].token)==0){ t[i].count++; find=1; } } if(find==0){ numword++; t[numword].token=(char*)malloc(strlen(s)+1); strcpy(t[numword].token,s); t[numword].count=1; } } } for(i=1;i<=numword;i++){ printf("buffer[%d]=%s count=%d\n",i,t[i].token,t[i].count); free(t[i].token); } printf("Number of words:%d\n",numword); free(t); fclose(fp); exit(0); }
お礼
ソースまで書いていただきありがとうございます。 私のソースとmaikoさんのソースを見比べたときに なるほど!!と思ったところがたくさんありました。 私の理解力が低くてすいません…。 わざわざありがとうございました。
- hashioogi
- ベストアンサー率25% (102/404)
いくつか問題があります。 (1) あなたの実行結果のcountは全て1になっていますが私の実行結果は1にならないところがあります。 それは構造体TOKEN_CHECKERの配列tのcountの初期化が抜けているところがあるからです。 (2) 以下が変です。 for(i=0;i<=numword;i++){ if(strcmp(t[numword].token,t[i].token)==0){ t[i].count++ } } printf("buffer[%d]=%s count=%d\n",numword,t[numword].token,t[numword].count); numword++; } 配列tの既に格納済みの部分と新しく最後に追加した部分のtokenを比較して一致したらcountに1を加算していますけど、途中で一致するものがあろうとなかろうと最後まで比較しています。そして更に途中で一致するものがあろうとなかろうとnumwordはインクリメントされます。 だから buffer[3]とbuffer[9]は共にcoatという結果になっています。 (3) 私の実行結果はNumber of words:10となりました。
お礼
回答ありがとうございます。 for(i=1;i<=24;i++){ t[i].count = 0; } を処理する前につけて、前に回答してくださった方と参考しながら実行してみましたが、セグメンテーションと出てしまいました。 tokenは初期設定しなくても大丈夫でしょうか??
- maiko0318
- ベストアンサー率21% (1483/6969)
1)単語をトークンに切ったあと、 bufferに入っているトークンを探してあれば+1なのに、 無条件に t[numword].token=s; で、トークンを登録してしまっている。 t[numword].count=0; で0にして t[i].count++; で1になる。 なので、 for(i=1;i<=numword;i++){ まず探して、(要素0は使わない) if(strcmp(t[numword].token,t[i].token)==0){ あれば t[i].count++; +1 break; ループを抜ける。 } } if(i==numword){ なければ numword++; t[numword].token=s; 登録して t[numword].count=1; カウント1 } 2)printf("buffer[%d]=%s count=%d\n",numword,t[numword].token,t[numword].count); せっかく探して+1するようにしているのに、トークンが出るたびにprintfしている。 全てのトークンの処理が終わってからまとめてprintfしないといけない。 上記2つで処理はできると思います。 あと、改善するなら 3)24個までにするなら25個めが出てきたらメモリーを壊すので24個登録したら処理を終わる。 もっと言えばトークンが出てくるたびに t=(TOKEN_CHECKER*)malloc(sizeof(TOKEN_CHECKER)); で1個づつメモリーを確保してつないでいくようにすると個数の制限はなくなる。
お礼
すぐの回答ありがとうございました。 早速直してみたんですが、セグメンテーションとなってしまいました。 maikoさんの通りに直してみたプログラミングは while( fgets(buffer,sizeof(buffer),fp)!=NULL){ buffer[ strlen(buffer)-1]='\0'; s = strtok(buffer,delimiter); if(s != NULL){ for(i=1;i<=numword;i++){ if(strcmp(t[numword].token,t[i].token)==0){ t[i].count++; break; } } if(i == numword){ numword++; t[numword].token = s; t[numword].count = 1; } numword++; } while((s=strtok(NULL,delimiter)) != NULL){ for(i=1;i<=numword;i++){ if(strcmp(t[numword].token,t[i].token)==0){ t[i].count++; break; } } if(i == numword){ numword++; t[numword].token = s; t[numword].count = 1; } } numword++; } for(i=0;i<=numword;i++){ printf("buffer[%d]=%s count=%d\n",i,t[i].token,t[i].count); } としてみましたが、何か物足りない所があるでしょうか?? すごい参考になったので回答してくれてうれしかったです。 ありがとうございます
お礼
まだプログラミングについてはあまり詳しくないほうなので、 文字列がどうやってアドレス番地に格納されるかなど 丁寧に教えていただきありがとうございます。 あとで自分でどういう構成で作りたかったのかを手書きで書いてみたいと思います。