C言語関係掲示板

過去ログ

No.950 英単語の種類と使用頻度

[戻る] [ホームページ]
No.12126

英単語の種類と使用頻度
投稿者---埼玉県(2004/01/25 14:52:11)


テキストファイル内の英文において出現する英単語の種類とその頻度を求め、頻度順に表示するプログラムを作成しています。(Windows XP)
本掲示板の過去ログを拝見し、利用させて頂いた結果、以下のようなプログラムとなりました。ここで問題なのですが、まず1点目が同様な英単語でも引用符やカンマなど語尾変化(例:penとpen./beとbe!/soとso?)しているものは別単語としてカウントしたい。2点目が同様な英単語でも始まりが大文字と小文字(例:Youとyou/Theとthe)では別単語としてカウントしたい場合はどのようにすれば良いのでしょうか?
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <ctype.h>

  #define SIZE 3000

  typedef struct {
    char *word;
    int count;
  } Word;

  int get_word(FILE *fp, char *word, int size)
  {
    int c, i = 0;

    do {
      if ((c = getc(fp)) == EOF) return EOF;
    } while (! isalpha(c));
      size--;
    do {
      if (i < size) word[i++] = tolower(c);
        c = getc(fp);
    } while (isalpha(c));
      word[i] = '\0';
      return i;
  }

  int compare(const void *a, const void *b)
  {
    const Word *p1 = a, *p2 = b;
    return p2->count - p1->count;
  }

  int main(int argc,char *argv[])
  {
    FILE *fp;
    Word table[SIZE];
    char word[128];
    int i,n = 0;

    fp = fopen(argv[1],"r");
    if (fp == NULL) {printf("cannot open file\n"); exit(0);}
    while (get_word(fp, word, sizeof word) != EOF){
      for (i = 0; i < n; i++)
        if (strcmp(word, table[i].word) == 0) break;
      if (i == n){
        if (n++ == SIZE) return 1;
          table[i].word = strdup(word);
            if (table[i].word == NULL) return 1;
              table[i].count = 1;
      }else table[i].count++;
    }
    qsort(table, n, sizeof *table, compare);
    for (i = 0; i &< n; i++)
      printf("%4d回: %s\n", table[i].count, table[i].word);
  
  fclose(fp);
  return 0;
  }

{表示例}
 3回:The
 2回:the
 2回:be!
 2回:be
 1回:so?
 1回:so


No.12127

Re:英単語の種類と使用頻度(追加)
投稿者---埼玉県(2004/01/25 15:13:49)


先ほどの投稿に追加質問があります。
C言語を理解していない質問になるかもしれませんが、どの程度の大きさのメモリを要求するかはsizeof関数、メモリの確保はmalloc関数で、通常動的メモリの確保にはこの両方の関数を使用すると教わりました。
そうすると先ほどの過去ログを利用したプログラムではmalloc関数は使用しなくても構わないのでしょうか?もし、malloc関数を使用する場合はどのようにプログラムすれば良いのでしょうか?

No.12129

Re:英単語の種類と使用頻度(追加)
投稿者---YuO(2004/01/25 15:39:11)


>C言語を理解していない質問になるかもしれませんが、
>どの程度の大きさのメモリを要求するかはsizeof関数、

理解していないようですね……。

sizeofは演算子です。関数ではありません。
一部の例外を除いて,sizeofはコンパイル時に値を決定します。

sizeofは,型又はオブジェクトの大きさを返す演算子です。
sizeof(char)は定義により1,
sizeof(void *)は32bit Windows用の処理系であれば大抵4,
int array[32];としたときのsizeof(array)は同じく大抵128になります。


>そうすると先ほどの過去ログを利用したプログラムではmalloc関数は使用しなくても構わないのでしょうか?

単語数を3000に限定していることと,strdupでmallocを隠しているために,
使用していないように見えます。

単語数を限定しない場合はmallocとreallocが使われます。
また,strdupという非標準の関数は,恐らく
char * strdup (const char * s)
{
    char * r;
    r = malloc(strlen(s) + 1);
    if (r) strcpy(r, s);
    return r;
}

に等しい処理を行いますから,実際にはmallocを利用しています。
#非標準の関数を使うなら処理系の情報をちゃんと提示して下さい。


No.12135

Re:英単語の種類と使用頻度(追加)
投稿者---埼玉県(2004/01/25 22:04:33)


strdupが非標準と言うことは自作してYuOさんのようなソースを書き加えれば良いのですね!
それと申し訳ありませんがもう一つ質問があります。
このようにするとメモリの開放であるfree()というものは必要ではないのでしょうか?私は、書かなくても良いと教わりました。
もし、必要であれば最初に投稿したプログラムの最後の方にfree(table[i]);と書けば良いのでしょうか?


No.12140

Re:英単語の種類と使用頻度(追加)
投稿者---YuO(2004/01/26 01:12:58)


>>int array[32];としたときのsizeof(array)は同じく大抵128になります。
>レスありがとうございます。と言うことは良くchar buf[128]などと書くこともこれから来ることなんですかね?

128とか256といった2の累乗の値が使われるのは,基本的に慣習的な話です。
環境によっては無駄がなくなったりアクセスが速くなったりというおまけが付いてくることもありますが,
基本的に特別な意味はありません。


>strdupが非標準と言うことは自作してYuOさんのようなソースを書き加えれば良いのですね!

strdup関数が存在しない環境では書き加える必要がありますが,
存在する環境では書き加える必要はありません。

存在する環境で利用する場合は,ライブラリのリファレンスをちゃんと読んでおく必要があります。


>このようにするとメモリの開放であるfree()というものは必要ではないのでしょうか?私は、書かなくても良いと教わりました。

状況や環境に依ります。

まず,確保したメモリをライブラリがそのままOSに返さず,
OSも確保されたメモリを回収しないような環境であれば,freeを書く必要があります。
#古いMS-DOS用のコンパイラではこのような状況が起きていた。

また長時間使うプログラム中では,メモリリークが積み重なってメモリが無くなり,
突如再現性の低いバグを引き起こす可能性があるのでfreeしておくべきです。

それ以外の場合は,個人orプロジェクトの判断になります。
Cプログラムにおけるメモリリークはリーク自体が問題にならない限り,
たいていの場合は問題にならないことが多いです。
#一般的なリソースリーク全体には言えないので注意。

mallocしたものを常にfreeすべきか,というのは簡単に論争になるテーマです。
C MAGAZINEの2004年2月号なども参考にしてみて下さい。


>もし、必要であれば最初に投稿したプログラムの最後の方にfree(table[i]);と書けば良いのでしょうか?

mallocで確保されているオブジェクトへのポインタが代入されているのはtable[i].wordです。
故に,これを解放する必要があります。つまり,
    for (i = 0; i < n; ++i) {
        free(table[i].word);
    }

となります。
freeの引数は,空ポインタか,malloc又はcalloc又はreallocの戻り値でないといけません。

No.12167

Re:英単語の種類と使用頻度(追加)
投稿者---埼玉県(2004/01/26 21:19:44)


Yuoさん色々な質問に対して教えて頂きありがとうございました。
今後とも宜しくお願い致します。

No.12128

Re:英単語の種類と使用頻度
投稿者---YuO(2004/01/25 15:29:47)


>本掲示板の過去ログを拝見し、利用させて頂いた結果、以下のようなプログラムとなりました。

利用するのではなく,理解して下さい。


>ここで問題なのですが、まず1点目が同様な英単語でも引用符やカンマなど語尾変化(例:penとpen./beとbe!/soとso?)しているものは別単語としてカウントしたい。

そうなるように組めばよいです。

ただ,省略形で使う'や単語を繋げたりワードラップで使う-はともかく,それ以外の記号は単語の一部ではありません。
当然,文の終了を表す.などは単語の一部ではなく,故に語尾でもありません。
#行末の-の扱いは結構ややこしい。単語を繋げる-なら削除しないがワードラップの-なら削除。
単語の定義をもっと詳しくしてみて下さい。


>2点目が同様な英単語でも始まりが大文字と小文字(例:Youとyou/Theとthe)では別単語としてカウントしたい場合はどのようにすれば良いのでしょうか?

プログラムを理解すればどこを変えればよいかはすぐわかると思います。
あなたが提示したプログラムでは,わざわざ大文字にして処理しているのですから。


No.12136

Re:英単語の種類と使用頻度
投稿者---埼玉県(2004/01/25 22:14:33)


>利用するのではなく,理解して下さい。

了解しました。

>>ここで問題なのですが、まず1点目が同様な英単語でも引用符やカンマなど語尾変化(例:penとpen./beとbe!/soとso?)しているものは別単語としてカウントしたい。
>
>そうなるように組めばよいです。
>
>ただ,省略形で使う'や単語を繋げたりワードラップで使う-はともかく,それ以外の記号は単語の一部ではありません。
>当然,文の終了を表す.などは単語の一部ではなく,故に語尾でもありません。
>#行末の-の扱いは結構ややこしい。単語を繋げる-なら削除しないがワードラップの-なら削除。
>単語の定義をもっと詳しくしてみて下さい。

ここが問題なのですが、今回の課題はこのような記号も単語の一部として取扱うことになっており、この部分を書くことができません。ご教示願います。

>プログラムを理解すればどこを変えればよいかはすぐわかると思います。
>あなたが提示したプログラムでは,わざわざ大文字にして処理しているのですから。

もしかすとisalphaとtolowerのところですか?



No.12137

Re:英単語の種類と使用頻度
投稿者---senshou(2004/01/25 22:52:01)


>もしかすとisalphaとtolowerのところですか?

get_word()のところを、
動きを追ってよく何をしているのかを理解してください。
そうすれば自ずと、どこをどうすればいいかが見えてくるでしょう。



No.12170

Re:英単語の種類と使用頻度
投稿者---埼玉県(2004/01/26 22:35:38)


>get_word()のところを、
>動きを追ってよく何をしているのかを理解してください。
>そうすれば自ずと、どこをどうすればいいかが見えてくるでしょう。

分かりました。順を追って理解します。
ところで最初に投稿したプログラムを下記のように書き換えました。
といってもmain()関数を最初に持ってきただけですが、このようなプログラムで宜しいのでしょうか?(先生からmain()関数は最初に書きなさいと言う指示だったため)申し訳ありませんがご教示願います。(その他の質問した件は何とか自分で解決します)
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <ctype.h>

  #define SIZE 1000

  typedef struct {
    char *word;
    int count;
  } Word;

  int get_word(FILE *, char *, int);
  int compare(const void *, const void *);

  int main(int argc,char *argv[])
  {
    FILE *fp;
    Word table[SIZE];
    char word[128];
    int i,n = 0;

    fp = fopen(argv[1],"r");
    if (fp == NULL) {printf("cannot open file\n"); exit(0);}
    while (get_word(fp, word, sizeof word) != EOF){
      for (i = 0; i < n; i++)
        if (strcmp(word, table[i].word) == 0) break;
      if (i == n){
        if (n++ == SIZE) return 1;
          table[i].word = strdup(word);
            if (table[i].word == NULL) return 1;
              table[i].count = 1;
      }else table[i].count++;
    }

    qsort(table, n, sizeof *table, compare);

    printf(" 回数  英単語\n");
    for (i = 0; i < n; i++)
      printf("%3d    %s\n", table[i].count, table[i].word);
      free(table[i].word);

  fclose(fp);
  return 0;
  }

  int get_word(FILE *fp, char *word, int size)
  {
    int c, i = 0;

    do {
      if ((c = getc(fp)) == EOF) return EOF;
    } while (! isalpha(c));
      size--;
    do {
      if (i < size) word[i++] = tolower(c);
        c = getc(fp);
    } while (isalpha(c));
      word[i] = '\0';
      return i;
  }

  int compare(const void *a, const void *b)
  {
    const Word *p1 = a, *p2 = b;
    return p2->count - p1->count;
  }

  char *strdup(const char *s)
  {
    char *p;

    p = malloc(strlen(s) + 1);
    if (p != NULL) strcpy(p, s);
    return p;
  }





No.12177

Re:英単語の種類と使用頻度
投稿者---senshou(2004/01/27 01:26:46)


>>get_word()のところを、
>>動きを追ってよく何をしているのかを理解してください。
>>そうすれば自ずと、どこをどうすればいいかが見えてくるでしょう。
>
>分かりました。順を追って理解します。
>ところで最初に投稿したプログラムを下記のように書き換えました。
>といってもmain()関数を最初に持ってきただけですが、このようなプログラムで宜しいのでしょうか?(先生からmain()関数は最初に書きなさいと言う指示だったため)申し訳ありませんがご教示願います。(その他の質問した件は何とか自分で解決します)

いくつか思ったことがあるのでそれを書きます。
以下間違いがあれば誰か突っ込んでください。
間違って覚えてるや勘違いしてるがあると思うので。

1.main()について

ISO規格では、
int main(void)

int main(char argc, char *argv[])
のどちらかでなければならない、となっています。
main()
はコンパイラに依る書き方ですが、通常はint main(void)として処理されるでしょう。
が、できれば上のどちらかの形式で書いたほうがいいと私は思います。

2.関数のプロトタイプ宣言

まずstrdup()のプロトタイプ宣言が抜けています。
それと
int func(int x);

int func(int);
はプロトタイプ宣言では引数名は無視されるので、
上の二つは同じ宣言ですが引数名は書いたほうが、読むとき分かりやすいと思います。

3.main()は最初に書かなければならないか

これは特別決まってはいませんが、main()は一番最初に読む所。
なので、(他の関数名を分かりやすくしておけば)そこを読むだけで、
何をしているのかがわかる。
そのため、最初に持ってきたほうが私は好きです。
とはいっても厳密には意識していません。

4.元々の質問について

get_word()の動作を理解した、中身を少し替えてみた、
けれども期待する動作はしなかった、
ていうときには、また聞いてください。

No.12215

Re:英単語の種類と使用頻度
投稿者---埼玉県(2004/01/27 22:45:04)


>いくつか思ったことがあるのでそれを書きます。
>以下間違いがあれば誰か突っ込んでください。
>間違って覚えてるや勘違いしてるがあると思うので。
>
>1.main()について
>
>2.関数のプロトタイプ宣言
>
>3.main()は最初に書かなければならないか
>
>4.元々の質問について
>
>get_word()の動作を理解した、中身を少し替えてみた、
>けれども期待する動作はしなかった、
>ていうときには、また聞いてください。

senshouさんありがとうございます。1.2.3.については勉強になりました。また教示頂いたことを以下のプログラムのように反映しました。

ところで4.元々の質問ですが、<ctype.h>内の関数全てについてもう一度確認し、get_word()の処理を色々と実施しましたが、「語尾変化(例:penとpen./beとbe!/soとso?)しているものは別単語としてカウントしたい。2点目が同様な英単語でも始まりが大文字と小文字(例:Youとyou/Theとthe)では別単語としてカウントしたい」といったところが出来ません。
例えば、beとbe!がbeと!になったりしてしまいます。
申し訳ありませんが宜しくお願い致します。
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <ctype.h>

  #define SIZE 1000

  typedef struct {
    char *word;
    int count;
  } Word;

  int get_word(FILE *fp, char *word, int size);
  int compare(const void *a, const void *b);
  char *strdup(const char *s);

  int main(int argc,char *argv[])
  {
    FILE *fp;
    Word table[SIZE];
    char word[128];
    int i,n = 0;

    fp = fopen(argv[1],"r");
    if (fp == NULL) {printf("cannot open file\n"); exit(0);}
    while (get_word(fp, word, sizeof word) != EOF){
      for (i = 0; i < n; i++)
        if (strcmp(word, table[i].word) == 0) break;
      if (i == n){
        if (n++ == SIZE) return 1;
          table[i].word = strdup(word);
            if (table[i].word == NULL) return 1;
              table[i].count = 1;
      }else table[i].count++;
    }

    qsort(table, n, sizeof *table, compare);

    printf(" 回数  英単語\n");
    for (i = 0; i < n; i++)
      printf("%3d    %s\n", table[i].count, table[i].word);
      free(table[i].word);

  fclose(fp);
  return 0;
  }

  int get_word(FILE *fp, char *word, int size)
  {
    int c, i = 0;

    do {
      if ((c = getc(fp)) == EOF) return EOF;
    } while (! isalpha(c));
      size--;
    do {
      if (i < size) word[i++] = tolower(c);
        c = getc(fp);
    } while (isalpha(c));
      word[i] = '\0';
      return i;
  }

  int compare(const void *a, const void *b)
  {
    const Word *p1 = a, *p2 = b;
    return p2->count - p1->count;
  }

  char *strdup(const char *s)
  {
    char *p;

    p = malloc(strlen(s) + 1);
    if (p != NULL) strcpy(p, s);
    return p;
  }





No.12218

Re:英単語の種類と使用頻度
投稿者---senshou(2004/01/27 23:38:58)


  int get_word(FILE *fp, char *word, int size)
  {
    int c, i = 0;

    do {
      if ((c = getc(fp)) == EOF) return EOF;
    } while (! isalpha(c));
      size--;
    do {
      if (i < size) word[i++] = tolower(c);
        c = getc(fp);
    } while (isalpha(c));
      word[i] = '\0';
      return i;
  }



このget_word()のはじめのdo-while文は何をしているか分かりますか?
特に、反復条件を絡めての話です。
次のdo-while文は何をしているか分かりますか?
反復条件とword[i++] = tolower(c)のところです。

No.12273

Re:英単語の種類と使用頻度
投稿者---埼玉県(2004/01/28 22:31:25)


><pre> int get_word(FILE *fp, char *word, int size)
{
int c, i = 0;

do {
if ((c = getc(fp)) == EOF) return EOF;
} while (! isalpha(c));
size--;
do {
if (i < size) word[i++] = tolower(c);
c = getc(fp);
} while (isalpha(c));
word[i] = '\0';
return i;
}

</pre>
>
>このget_word()のはじめのdo-while文は何をしているか分かりますか?
>特に、反復条件を絡めての話です。
>次のdo-while文は何をしているか分かりますか?
>反復条件とword[i++] = tolower(c)のところです。

説明はヘタクソなので平易に述べさせて頂きます。
初めのdo-while文内ですが、getcでファイル内を読み込みファイルが終りであればEOFを返す。また読取エラーであればEOFを返す。(取り合えず文を1回実行してから継続条件式の判定を行う)
次にwhile内の継続条件式であるc内の文字がisalpha(英大文字か英小文字か)で行い真ならループにし、sizeを一つずつ減らす。偽になったらループから抜け出し次の処理を行う。

続いて、もしiがsizeよりも小さい場合、c内の文字をtolower(英大文字を英小文字に変換)で行い、iを一つずつ増やしword内に入れ、またgetcでファイル内を読み込み、それをcに置く。次にwhile内の継続条件式は前のwhile内処理に!がないだけであり、最後にiならばループを終了。

本当にヘタクソな説明ですみません。


No.12274

Re:英単語の種類と使用頻度
投稿者---isshi(2004/01/28 23:26:17)


>sizeを一つずつ減らす
do - while の外にあるので、ひとつずつではなく1つだけ減らします。
なぜ、1つだけ減らしているかわかりますか?

>isalpha(英大文字か英小文字か)
isalphaは英大文字か英小文字を判別するものではありません。

>最後にiならばループを終了
違います。
インデントの入れ方からして、どうも do - while を
理解していないように見えます。
do {
  // ここがループされる
} while (条件)
// ループを抜けるとここに来る

また、苦労して説明を書かれたようですが、単にコードを日本語に
書き直しただけのように見えます。
結局、それぞれの do - while が何をする目的のためにあるか
わかりましたか?



No.12275

Re:英単語の種類と使用頻度
投稿者---埼玉県(2004/01/28 23:48:38)


>結局、それぞれの do - while が何をする目的のためにあるか
>わかりましたか?

すみません。一つお聞きしたいのですが、基本的にdo-while文は、まず文を1回実行してから、継続条件式の判定を行う。継続条件式が真であればループする。偽になったらループから抜けて次の処理を行うものではないのですか?



No.12277

Re:英単語の種類と使用頻度
投稿者---senshou(2004/01/29 01:08:13)


>すみません。一つお聞きしたいのですが、基本的にdo-while文は、まず文を1回実行してから、継続条件式の判定を行う。継続条件式が真であればループする。偽になったらループから抜けて次の処理を行うものではないのですか?

その通りです。
do-while文は後判定の反復制御文です。

ふたつのdo-while文の意味が分かりますか?と聞いたのは、
順になにをしているかではなく、
それによってなにが出来ているのか、ということです。



No.12323

Re:英単語の種類と使用頻度
投稿者---埼玉県(2004/01/29 20:00:23)


>その通りです。
>do-while文は後判定の反復制御文です。
>
>ふたつのdo-while文の意味が分かりますか?と聞いたのは、
>順になにをしているかではなく、
>それによってなにが出来ているのか、ということです。
>

申し訳ありませんでした。
ファイル内の英単語を読み込み、もしその英単語が大文字であれば小文字に変換され、それが同じ英単語あればカウントし、違う単語であれば1から数え上げると言うことではないのでしょうか?宜しくお願い致します。



No.12329

Re:英単語の種類と使用頻度
投稿者---senshou(2004/01/30 00:21:33)


>申し訳ありませんでした。
>ファイル内の英単語を読み込み、もしその英単語が大文字であれば小文字に変換され、それが同じ英単語あればカウントし、違う単語であれば1から数え上げると言うことではないのでしょうか?宜しくお願い致します。

ちゃんとソースは読んでみましたか?
見たところ「単語」そのものを小文字に変換しているわけではありません。
さらに、カウントは別のところでしているはずです。

#%(Hello, world)&;
このような(適当な且つ具体的な)文字列をget_word()に渡したらどういうふうに処理するかを考えてみてください。



No.12380

Re:英単語の種類と使用頻度
投稿者---埼玉県(2004/01/31 20:34:29)


>ちゃんとソースは読んでみましたか?
>見たところ「単語」そのものを小文字に変換しているわけではありません。
>さらに、カウントは別のところでしているはずです。
>
>#%(Hello, world)&;
>このような(適当な且つ具体的な)文字列をget_word()に渡したらどういうふうに処理するかを考えてみてください。
>

senshouさん度々申し訳ありません。
かずまさんにもレス頂きましたが、get_word()内の処理が勉強不足(頭が悪い)で理解できていません。do-while文内の反復処理も含めて申し訳ありませんがレス願います。



No.12385

Re:英単語の種類と使用頻度
投稿者---senshou(2004/01/31 22:18:58)


>senshouさん度々申し訳ありません。
>かずまさんにもレス頂きましたが、get_word()内の処理が勉強不足(頭が悪い)で理解できていません。do-while文内の反復処理も含めて申し訳ありませんがレス願います。

理解できないのは頭が悪いせいではありません。
そんなことが理由だったら私も出来ないことになってしまいます(w
絶対的に基本的な知識が足りないのもありますでしょうが、
それより1行1行なにをしているのか、と具体的に何か短いものを用意して動作を追っていくことです。

ということで最初のdo-while文を見てみましょう。
do {
if ((c = getc(fp)) == EOF) return EOF;
} while (! isalpha(c));

do-while文の中のif文の条件は毎回チェックされますよね?
つまりif文の条件中のc = getc(fp)でcに文字を読み込んでいるわけです。
まずcに文字を読んでそれがEOFかどうか。(そしてEOFならばファイルの終わりということを示さなきゃならんので、EOFを返す)
この処理を読み込んだ文字cがアルファベットでない限り繰り返しているわけです。
具体的に
#(Hello, world);EOF
という英文を読んだとします。
まずcに'#'が読み込まれます。これはEOFではないのでEOFは返しません。
さらにアルファベットではないので、ループをし、if文の条件でまたcに次の文字'('を読みます。
これもアルファベットではないので、同じことをします。
今度はcに'H'が読み込まれます。
これはアルファベットなのでループを抜けます。
cには'H'が入りっぱなしなのを覚えておいてください。

次の文には
size--;
があります。
word[]には単語を格納するのですよね?つまり文字列です。
なので、ナル文字('\0')が必要になります。
それを入れる場所を確保しておくために、sizeをひとつ減らしているわけです。
このことは次のdo-while文が分かれば、理解できるでしょう。

長くなりましたので、一回きります。



No.12389

Re:英単語の種類と使用頻度
投稿者---senshou(2004/01/31 23:34:17)


次のdo-while文を見てみましょう。
do {
if (i < size) word[i++] = tolower(c);
c = getc(fp);
} while (isalpha(c));

#(Hello, world);
のHまできたところですね。
このdo-while文では反復条件が変わっています。
読み込んだ文字cがアルファベットならループするようです。
では具体的に見ていきましょう。

cには'H'が入っています。
if文でi < sizeをチェックしています。
word[]の1要素に1文字入れたいので、word[]のサイズsizeを超えたらいかんのです。
で、はじめはi = 0で、sizeは十分にとってあるとします。
word[i++] = tolower(c);
というのは
word[i] =tolower(c);
i++;
と同じことです。
cに入っている文字('H'でしたね)を小文字に変換し、word[0]に格納し、iを1進めているわけです。
で、1文字読み込む。
次のもじは'e'、つまりアルファベットなので、ループを繰り返します。
これを小文字に変換し(もともと小文字だから変えないで)、word[1]に入れ、iを1進めて2にする。
これを繰り返していくと、word[4]に'o'が読み込まれます。
そしてiを1進め5にしてからcに次の文字','が読み込まれます。
これはアルファベットではないので、ループを抜けます。(cには','が入りっぱなしです)
それで次の文
word[i] = '\0';
でナル文字を入れると。
今の例ではi = 5でしたから、word[5]に'\0'が入ります。

ここで、word[]の中身を確認すると、
word[0] = 'h'
word[1] = 'e'
word[2] = 'l'
word[3] = 'l'
word[4] = 'o'
word[5] = '\0'
となっていて、確かに最初の単語helloが格納されています。
これをどこか別のところにとっておいて(先のプログラムではstrdup()を使っていました)もう一回get_word()を呼び出せば、
#(Hello, world);EOF
の,からまた同じ処理が始まり、
word[0] = 'w'
word[1] = 'o'
word[2] = 'r'
word[3] = 'l'
word[4] = 'd'
word[5] = '\0'
と次の単語worldがword[]に格納されます。


これを踏まえて、get_word()を変えてみると、
最初のdo-while文ではいらない文字を飛ばしているのでした。
反復条件を !isalpha(c) とすれば、アルファベットでない文字を飛ばしていることになります。
あなたの条件を見る限り、英文テキストを読み込む、!や.などはくっつけておきたいということなので、読み飛ばす文字は空白文字だけでいいでしょう。
ということで、反復条件を isspace(c) とします。

次のdo-while文は実際に単語を1文字ずつword[]に格納していくのでした。
反復条件を isalpha(c) とすればアルファベットのみをword[]に格納していきます。
アルファベットだけでなく、.!?なども一緒に読み込みたい、つまり空白文字以外を読めばよさそうなので、反復条件を !isspace(c) とすればいいでしょう。
あとは大文字小文字の区別をつけたいとのことなので、これは単純に、tolower()で小文字にしてしまわずに、cをそのまま格納すればいいと思います。

No.12404

Re:英単語の種類と使用頻度
投稿者---埼玉県(2004/02/01 18:34:14)


senshouさん親切かつ丁寧なレスありがとうございました。
おかげでget_word内の処理が理解できました。
申し訳ありませんがもう少し質問させて下さい。
レス頂いたことを以下のように反映しましたが、コンパイル時に固まってしまい(実行に移らない)、結果を表示してくれません。これって私の環境(Win XP/bcc)のせいなんですかね?

 int get_word(FILE *fp, char *word, int size)
 {
  int c, i = 0;

  do {
   if ((c = fgetc(fp)) == EOF) return EOF;
  } while (isspace(c));
   size--;
  do {
   if (i &lt; size) word[i++] = c;
    c = fgetc(fp);
  } while (! isspace(c));
   word[i] = '\0';
   return i;
 }

それとこのcompare内ですが、いまいち理解できていません。
 int compare(const void *a, const void *b)
 {
  const Word *p1 = a, *p2 = b;
  return p2-&gt;count - p1-&gt;count;
 }
qsortの第4引数に渡すための比較関数なのでしょうか?

No.12406

Re:英単語の種類と使用頻度
投稿者---senshou(2004/02/01 20:25:07)


compare()はqsort()に必要な比較関数です。

それと実行できないとのことでしたが、私も実行は出来ましたが、途中で止まってしまいました。
原因は以下の部分です。
for (i = 0; i < n; i++)
    printf("%3d    %s\n", table[i].count, table[i].word);
    free(table[i].word);

free()するのはtable[i]のi = 0からi = n - 1なのでfree()もfor文に入れてあげなくてはいけません。
で、おそらく動くだろうと思われるのが以下のプログラムです。

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

#define SIZE 1000

typedef struct {
    char *word;
    int count;
} Word;

int get_word(FILE *fp, char *word, int size);
int compare(const void *a, const void *b);
char *strdup(const char *s);

int main(int argc,char *argv[])
{
    FILE *fp;
    Word table[SIZE];
    char word[128];
    int i,n = 0;

    fp = fopen(argv[1],"r");
    if (fp == NULL) {
        printf("cannot open file\n");
        exit(0);
    }
    
    while (get_word(fp, word, sizeof word) != EOF) {
        for (i = 0; i < n; i++)
            if (strcmp(word, table[i].word) == 0) break;
        if (i == n){
            if (n++ == SIZE) return 1;
            table[i].word = strdup(word);
            if (table[i].word == NULL) return 1;
            table[i].count = 1;
            } else table[i].count++;
    }

    qsort(table, n, sizeof *table, compare);

    printf(" 回数  英単語\n");
    for (i = 0; i < n; i++) {
        printf("%3d    %s\n", table[i].count, table[i].word);
        free(table[i].word);
    }

    fclose(fp);
    return 0;
}

int get_word(FILE *fp, char *word, int size)
{
    int c, i = 0;

    do {
        if ((c = getc(fp)) == EOF) return EOF;
    } while (isspace(c));
    size--;
    do {
        if (i < size) word[i++] = c;
        c = getc(fp);
    } while (! isspace(c));
    word[i] = '\0';
    return i;
}

int compare(const void *a, const void *b)
{
    const Word *p1 = a, *p2 = b;
    return p2->count - p1->count;
}

char *strdup(const char *s)
{
    char *p;

    p = malloc(strlen(s) + 1);
    if (p != NULL) strcpy(p, s);
    return p;
}


No.12423

Re:英単語の種類と使用頻度
投稿者---埼玉県(2004/02/02 19:24:37)


>compare()はqsort()に必要な比較関数です。
>
>それと実行できないとのことでしたが、私も実行は出来ましたが、途中で止まってしまいました。
>原因は以下の部分です。
><pre>for (i = 0; i < n; i++)
printf("%3d %s\n", table[i].count, table[i].word);
free(table[i].word);
</pre>
>free()するのはtable[i]のi = 0からi = n - 1なのでfree()もfor文に入れてあげなくてはいけません。

度々レスありがとうございます。
ん〜どうしてもこのように変更してもコンパイル時止まってしまいます。

No.12434

Re:英単語の種類と使用頻度
投稿者---senshou(2004/02/02 23:45:47)


>度々レスありがとうございます。
>ん〜どうしてもこのように変更してもコンパイル時止まってしまいます。

私の環境WinXp,bcc5.5.1 for win32ではちゃんとコンパイルもでき、実行もできています。
以下にうまくいったソースと試しにそのソースを読ませた結果を載せます。

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

#define SIZE 1000

typedef struct {
    char *word;
    int count;
} Word;

int get_word(FILE *fp, char *word, int size);
int compare(const void *a, const void *b);
char *strdup(const char *s);

int main(int argc,char *argv[])
{
    FILE *fp;
    Word table[SIZE];
    char word[128];
    int i,n = 0;

    fp = fopen(argv[1],"r");
    if (fp == NULL) {
        printf("cannot open file\n");
        exit(0);
    }
    
    while (get_word(fp, word, sizeof word) != EOF) {
        for (i = 0; i < n; i++)
            if (strcmp(word, table[i].word) == 0) break;
        if (i == n){
            if (n++ == SIZE) return 1;
            table[i].word = strdup(word);
            if (table[i].word == NULL) return 1;
            table[i].count = 1;
            } else table[i].count++;
    }

    qsort(table, n, sizeof *table, compare);

    printf(" 回数  英単語\n");
    for (i = 0; i < n; i++) {
        printf("%3d    %s\n", table[i].count, table[i].word);
        free(table[i].word);
    }

    fclose(fp);
    return 0;
}

int get_word(FILE *fp, char *word, int size)
{
    int c, i = 0;

    do {
        if ((c = getc(fp)) == EOF) return EOF;
    } while (isspace(c));
    size--;
    do {
        if (i < size) word[i++] = c;
        c = getc(fp);
    } while (! isspace(c));
    word[i] = '\0';
    return i;
}

int compare(const void *a, const void *b)
{
    const Word *p1 = a, *p2 = b;
    return p2->count - p1->count;
}

char *strdup(const char *s)
{
    char *p;

    p = malloc(strlen(s) + 1);
    if (p != NULL) strcpy(p, s);
    return p;
}

以下実行結果
回数 英単語
14 =
11 }
10 {
10 int
9 char
8 if
7 return
6 ==
5 0;
4 (i
4 #include
4 void
3 NULL)
3 while
3 1;
3 const
3 i
3 <
2 *strdup(const
2 get_word(FILE
2 *fp,
2 *word,
2 sizeof
2 Word
2 !=
2 EOF)
2 *a,
2 for
2 compare(const
2 size)
2 i++)
2 n;
2 do
1 word)
1 table[i].word)
1 0)
1 break;
1 n){
1 (n++
1 SIZE)
1 table[i].word
1 strdup(word);
1 (table[i].word
1 table[i].count
1 else
1 table[i].count++;
1 qsort(table,
1 n,
1 *table,
1 compare);
1 printf("
1 回数
1 英単語\n");
1 printf("%3d
1 %s\n",
1 table[i].count,
1 table[i].word);
1 free(table[i].word);
1 fclose(fp);
1 c,
1 ((c
1 getc(fp))
1 EOF;
1 (isspace(c));
1 size--;
1 word[i++]
1 c;
1 c
1 getc(fp);
1 (!
1 isspace(c));
1 word[i]
1 '\0';
1 i;
1 *b)
1 *p1
1 a,
1 *p2
1 b;
1 p2->count
1 -
1 p1->count;
1 *s)
1 *p;
1 p
1 malloc(strlen(s)
1 +
1 1);
1 (p
1 strcpy(p,
1 s);
1 p;
1 word,
1 (get_word(fp,
1 exit(0);
1 file\n");
1 open
1 printf("cannot
1 (fp
1 fopen(argv[1],"r");
1 fp
1 i,n
1 word[128];
1 table[SIZE];
1 *fp;
1 FILE
1 *argv[])
1 argc,char
1 main(int
1 *s);
1 *b);
1 size);
1 Word;
1 count;
1 *word;
1 struct
1 typedef
1 1000
1 SIZE
1 #define
1 <ctype.h>
1 <string.h>
1 <stdlib.h>
1 <stdio.h>
1 (strcmp(word,

と、このようになりました。
まさかとは思いますが、
can not file open
と表示されているとかいうオチではありませんよね?

No.12435

Re:英単語の種類と使用頻度
投稿者---senshou(2004/02/03 01:42:59)


>まさかとは思いますが、
>can not file open
>と表示されているとかいうオチではありませんよね?

コンパイルすらできないのならば、実行出来るはずはないですね。
コンパイルが通らないのならば、エラーが出てるはずです。
それを書いてもらえるとなにかわかるかもしれません。

No.12452

Re:英単語の種類と使用頻度
投稿者---埼玉県(2004/02/03 23:43:35)


>>まさかとは思いますが、
>>can not file open
>>と表示されているとかいうオチではありませんよね?
>
>コンパイルすらできないのならば、実行出来るはずはないですね。
>コンパイルが通らないのならば、エラーが出てるはずです。
>それを書いてもらえるとなにかわかるかもしれません。

度々すみません。
やはりコンパイルは通りますが、最後の結果出力の時止まってしまうと
言うか、例えばProgs>purog data.txtを入力し、リターンキーを押した後、
カーソルが点滅状態でその先に進みません。(エラーなし)
環境及びプログラムはsenshouさんとまったく同じなのに!

それで若しかすると全然検討違いかも知れませんが、私が読み込みたい
テキストファイルに問題あり若しくはプログラムの方でそれに合ったよ
うにプログラムされていない(後者であれば私の始めの質問の仕方がバ
ツですね)
で参考までに読み込みたいテキストファイル内の一例を下記に書きます。

You are an American business person.
You are at the meeting point at Terminal two Tokyo
International Airport. Student A, who is from the
Tokyo office, is supposed to meet you but he isn't
there! Call Student A and ask for directions to
get to Tokyo by yourself.



No.12453

Re:英単語の種類と使用頻度
投稿者---senshou(2004/02/04 00:12:30)


例とやらを処理させたところ、
回数 英単語
3 to
3 Tokyo
2 You
2 is
2 at
2 are
2 Student
2 the
1 two
1 International
1 Airport.
1 A,
1 who
1 office,
1 supposed
1 meet
1 you
1 but
1 he
1 isn't
1 there!
1 Call
1 A
1 and
1 ask
1 for
1 directions
1 get
1 by
1 yourself.
1 Terminal
1 point
1 meeting
1 person.
1 business
1 American
1 an
1 from
のように無事実行できました。
ファイルの実行方法に問題があります。
どうやらコマンドライン引数についての勉強が足りないようです。
http://www9.plala.or.jp/sgwr-t/c/sec11-4.html
を参考に。

No.12456

Re:英単語の種類と使用頻度
投稿者---senshou(2004/02/04 01:48:53)


>ファイルの実行方法に問題があります。
>どうやらコマンドライン引数についての勉強が足りないようです。
>http://www9.plala.or.jp/sgwr-t/c/sec11-4.html
>を参考に。

Progs>purog data.txt
と入力したとありますが、いったいどれが実行ファイルでしょうか?
data.txtは処理させる英文ファイルだと思うので、
purogが実行ファイルでしょうか?
とすると、Progs>はいったいなんでしょう?

No.12459

Re:英単語の種類と使用頻度
投稿者---NykR(2004/02/04 08:01:07)


>とすると、Progs>はいったいなんでしょう?

ディレクトリまたはフォルダ名でしょう

で、今回のようなことが起こる原因は、
2度目のdo-whileにEOF対策が無いことだと思われます。

No.12462

Re:英単語の種類と使用頻度
投稿者---NykR(2004/02/04 09:14:35)


>で、今回のようなことが起こる原因は、
>2度目のdo-whileにEOF対策が無いことだと思われます。

元のdata.txtは、おそらく末尾に(改行等の)空白文字がないのでしょう、
その場合、2度目の do-while で EOF まで読み込まれます。
そして、ispace(EOF) == 0 なので、更に do-while の中の処理を繰り返します。
このとき、fp のヘッダはファイルの末尾にあるので、
再び EOF が読み込まれ、結果として無限ループになるわけです。

No.12464

Re:英単語の種類と使用頻度
投稿者---senshou(2004/02/04 15:11:07)


>>で、今回のようなことが起こる原因は、
>>2度目のdo-whileにEOF対策が無いことだと思われます。
>
>元のdata.txtは、おそらく末尾に(改行等の)空白文字がないのでしょう、
>その場合、2度目の do-while で EOF まで読み込まれます。
>そして、ispace(EOF) == 0 なので、更に do-while の中の処理を繰り返します。
>このとき、fp のヘッダはファイルの末尾にあるので、
>再び EOF が読み込まれ、結果として無限ループになるわけです。

なっ・・・本当だ・・・
どうやら完全版と別のを載せていたようです。(ていうか埼玉県さんも気づいてくれー)
というわけで、多分大丈夫なのを載せます。

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

#define SIZE 1000

typedef struct {
    char *word;
    int count;
} Word;

int get_word(FILE *fp, char *word, int size);
int compare(const void *a, const void *b);
char *strdup(const char *s);

int main(int argc,char *argv[])
{
    FILE *fp;
    Word table[SIZE];
    char word[128];
    int i,n = 0;

    fp = fopen(argv[1],"r");
    if (fp == NULL) {
        printf("cannot open file\n");
        exit(0);
    }
    
    while (get_word(fp, word, sizeof word) != EOF) {
        for (i = 0; i < n; i++)
            if (strcmp(word, table[i].word) == 0) break;
        if (i == n){
            if (n++ == SIZE) return 1;
            table[i].word = strdup(word);
            if (table[i].word == NULL) return 1;
            table[i].count = 1;
            } else table[i].count++;
    }

    qsort(table, n, sizeof *table, compare);

    printf(" 回数  英単語\n");
    for (i = 0; i < n; i++) {
        printf("%3d    %s\n", table[i].count, table[i].word);
        free(table[i].word);
    }

    fclose(fp);
    return 0;
}

int get_word(FILE *fp, char *word, int size)
{
    int c, i = 0;

    do {
        if ((c = getc(fp)) == EOF) return EOF;
    } while (isspace(c));
    size--;
    do {
        if (i < size) word[i++] = c;
        if ((c = getc(fp)) == EOF) return EOF;
    } while (! isspace(c));
    word[i] = '\0';
    return i;
}

int compare(const void *a, const void *b)
{
    const Word *p1 = a, *p2 = b;
    return p2->count - p1->count;
}

char *strdup(const char *s)
{
    char *p;

    p = malloc(strlen(s) + 1);
    if (p != NULL) strcpy(p, s);
    return p;
}


No.12466

Re:英単語の種類と使用頻度
投稿者---senshou(2004/02/04 17:04:35)


さきほどのプログラムだとどうやら最後の単語(例で言えばyourself.)が読み込まれないようです。
色々やってみたが、直らない・・・
脳内ではうまくいっているのですが・・・なにか勘違いしているようです。
もういちどよく考えて見ます。

No.12468

Re:英単語の種類と使用頻度
投稿者---たか(2004/02/04 20:21:03)


>さきほどのプログラムだとどうやら最後の単語(例で言えばyourself.)が読み込まれないようです。
>色々やってみたが、直らない・・・
>脳内ではうまくいっているのですが・・・なにか勘違いしているようです。
>もういちどよく考えて見ます。

最後の行に0x0a(LF)コードが含まれてないと、関数get_word()はEOFを
返してしまいます。この場合EOFを返さないように何らかの工夫が必要
でしょう。

No.12472

Re:英単語の種類と使用頻度
投稿者---埼玉県(2004/02/04 22:09:23)


皆様にはご迷惑をお掛けしております。
今この問題が解決致しました。
基本的にはsenshouさんが以前投稿してくれたプログラム
(前後省略get_word内のみを下記に記載)で結果を得る
ことが出来ました。
<pre
 int get_word(FILE *fp, char *word, int size)
 {
  int c, i = 0;

  do {
   if ((c = getc(fp)) == EOF) return EOF;
  } while (isspace(c));
   size--;
  do {
   if (i &lt; size) word[i++] = c;
    c = getc(fp);
  } while (! isspace(c));
   word[i] = '\0';
   return i;
 }

で何が問題だったかと言うと、やはり私のテキストファイルに問題
(EOFの位置)がありました。
NykRさんのレスの通り
>末尾に(改行等の)空白文字が無く、EOFまで読み込まれ
>結果として無限ループ
このことですが、以下に問題のテキストファイル内を記載します。

【変更前(最後の部分だけ)】
get to Tokyo by yourself.[EOF]←ここにある
【変更後】
get to Tokyo by yourself.
[EOF]←ここに変更

これって私の単純ミス。

senshouさんにはこの問題に対して色々とご指導頂きありがとうございました。
NykRさん及びたかさんもレス頂きありがとうございました。
私のいたらない点が多いあまりに皆様には簡単な問題をより難しい問題にして
しまったことを深くお詫び申し上げます。

でもEOFが変更前の位置だと・・・ん〜?


No.12521

Re:英単語の種類と使用頻度
投稿者---senshou(2004/02/06 01:43:23)


>最後の行に0x0a(LF)コードが含まれてないと、関数get_word()はEOFを
>返してしまいます。この場合EOFを返さないように何らかの工夫が必要
>でしょう。

get_word()のほうばっかり見てました。
あそこ(2個目のdo-while文)でEOFを返してしまうと、
word[]に単語は格納されるが、main()での処理がされないまま終わってしまうんですね。
で、1個目のdo-whileでEOFを返させるようにbreak;にすればいいと。

まだまだ視野が狭く、経験、知識が浅いようです。


No.12470

Re:英単語の種類と使用頻度
投稿者---isshi(2004/02/04 21:39:03)


2つめの return EOF; を break; にするだけだと思います。

No.12476

Re:英単語の種類と使用頻度
投稿者---埼玉県(2004/02/04 22:34:49)


でもEOFが変更前の位置だと・・・ん〜?
この問題も

>2つめの return EOF; を break; にするだけだと思います。

こうすることでこの問題も解決しました。
isshiさんレスありがとうございました。


No.12469

Re:英単語の種類と使用頻度
投稿者---埼玉県(2004/02/04 21:16:28)


>Progs>purog data.txt
>と入力したとありますが、いったいどれが実行ファイルでしょうか?
>data.txtは処理させる英文ファイルだと思うので、
>purogが実行ファイルでしょうか?
>とすると、Progs>はいったいなんでしょう?

申し訳ありません。
私の説明不足で皆様にはご迷惑をお掛けしております。
ここで言っているのは、コマンドプロンプト画面の最後
の部分をそのままコピペしただけです。
purogが実行ファイルでdata.txtが英文のテキストファイルです。


No.12333

Re:英単語の種類と使用頻度
投稿者---かずま(2004/01/30 13:11:36)


> まず1点目が同様な英単語でも引用符やカンマなど語尾変化(例:penと
> pen./beとbe!/soとso?)しているものは別単語としてカウントしたい。
> 2点目が同様な英単語でも始まりが大文字と小文字(例:Youとyou/Theとthe)
> では別単語としてカウントしたい場合はどのようにすれば良いのでしょうか?

語尾変化というのが理解できません。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define SIZE  1000

typedef struct { char *word; int count; } Word;

int compare(const void *a, const void *b)
{
    const Word *p1 = a, *p2 = b;  return p2->count - p1->count;
}

int main(void)
{
    Word table[SIZE];  char word[1024], s[2] = "";  int i, n = 0;

    while (scanf("%*[^a-zA-Z]"), scanf("%[a-zA-Z]%c", word, s) == 2) {
        if (!isspace(*s & 0xFF)) strcat(word, s);
        for (i = 0; i < n; i++)
            if (strcmp(word, table[i].word) == 0) break;
        if (i == n) {
            if (n++ == SIZE) return 1;
            table[i].word = strdup(word);
            if (table[i].word == NULL) return 1;
            table[i].count = 1;
        }
        else
            table[i].count++;
    }
    qsort(table, n, sizeof *table, compare);
    for (i = 0; i < n; i++)
        printf("%3d  %s\n", table[i].count, table[i].word);
    return 0;
}


No.12336

Re:英単語の種類と使用頻度
投稿者---かずま(2004/01/30 15:37:00)


語尾変化というのが何なのか理解できませんが、. , !  ? を付けることだと解釈し、

    if (!isspace(*s & 0xFF)) strcat(word, s);
を
    if (strchr(".,!?", *s)) strcat(word, s);

に変更しておきます。


No.12379

Re:英単語の種類と使用頻度
投稿者---埼玉県(2004/01/31 20:21:49)


>
語尾変化というのが何なのか理解できませんが、. , !  ? を付けることだと解釈し、

    if (!isspace(*s & 0xFF)) strcat(word, s);
を
    if (strchr(".,!?", *s)) strcat(word, s);

に変更しておきます。

私の質問の意図が不明であったことをお詫び申し上げます。
かずまさんの言うとおり語尾変化というのは、. , ! ? を付けることです。
プログラム(C言語)と言うのはやはり一つ一つ処理を理解し、数多く組まないと分からないものなのでしょうか?
私はC言語を勉強し始めて数ヶ月で、色々な参考書を購入し勉強していますが、期待する結果を得るまでに何日もかかってしまいます。



No.12381

Re:英単語の種類と使用頻度(追加)
投稿者---埼玉県(2004/01/31 21:08:31)


int main(void)
{
Word table[SIZE]; char word[1024], s[2] = ""; int i, n = 0;

while (scanf("%*[^a-zA-Z]"), scanf("%[a-zA-Z]%c", word, s) == 2) {
if (!isspace(*s & 0xFF)) strcat(word, s);

かずまさん申し訳ありません追加でお聞きしたいことがあります。
scanfを使う場合、私はscanf(フォーマット,変数1,変数2,...,変数n)、fscanfとの違いは第1引数がないだけだと教わりました。
かずまさんがここで書かれている内容が勉強不足でどのような処理を行っているのかが分かりません。