単語の抽出のCプログラム

List.1はテキストファイル中から単語を抜き出すCプログラムの例である。

数字から始まる文字列は疑似単語として排除している。
アルファベットから始まっていて,単語のように見えても,その直前の文字が数字の場合は単語ではない。
疑似単語として排除されるべき文字列の例 12qwe

List 1 extractWord.c

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

int main()
{
    char fname[256]; /*ファイル名*/
    FILE *fp;        /*ファイルストリーム*/
    int ch,oldch=0;          /*読み込んだ文字*/
    int NowInWord=0; /*単語検出中は1,疑似単語検出中は-1,そうでない時は0*/
    int count=0;     /*見つけた単語の数*/
    printf("file name : ");
    gets(fname);
   
    fp=fopen(fname,"r");
    if (fp==NULL) {
        printf("can't open %s\n",fname);
        exit(1);
    }
   
    while ((ch=fgetc(fp)) != EOF) {
        /*ファイルから1文字読み込んでchに代入して,それがEOFでなかったら*/
        if (NowInWord==1) { /*単語読み込み中ならば*/
            if (isalnum(ch)||ch=='_') { /*chがアルファベット,数字または_だったら*/
                putchar(ch);
            } else { /*単語の外に出たぞ*/
                NowInWord=0;
                count++;
                if (count%8==0) { /*8回に一回改行したいなー*/
                    printf("\n");
                } else {
                    printf(" ");
                }
            }
        } else if (NowInWord==-1) { /*疑似単語読み込み中ならば*/
            if (isalnum(ch)||ch=='_') { /*chがアルファベット,数字または_だったら*/
            } else { /*疑似単語の外に出たぞ*/
                NowInWord=0;
            }
        } else { /*単語読み込み中でないならば*/
            if (isalpha(ch)) { /*chがアルファベットだったら*/
                if (isdigit(oldch)) {
                    NowInWord=-1;
                } else {
                    NowInWord=1;
                    putchar(ch);
                }
            }
        }
        oldch=ch;
    }
   
    fclose(fp);
}

/*
対象としたtest.c
------------------------------------------------------
#include <stdio.h>

int main()
{
    printf("Hello. How are you?\n");
    printf("Fine, thanks. And You?\n");
    printf("So so.\n");
    return 0;
}
------------------------------------------------------

実行結果
file name : test.c
include stdio h int main printf Hello How
are you n printf Fine thanks And You
n printf So so n return



対象としたtest.txt
------------------------------------------------------
1234 ..wer qwe123+wer456
sw_qwe,q12_12,12qwe,A123B123
wsed_123

------------------------------------------------------

実行結果
file name : test.txt
wer qwe123 wer456 sw_qwe q12_12 A123B123 wsed_123


*/


上記List.1のプログラムは1行読み込み,単語抽出,表示のすべてをmain中で行っている。
これは腕力で作成した見通しが悪いプログラムである。

関数のところで段階詳細化(ボトムアップ)プログラミングを学んだので,それを生かしてみよう。
段階詳細化(ボトムアップ)の極意は,「このような便利な機能があったらいいな」を自分で作ることである。
どのような機能をひとまとめにすると都合が良いかは,いろいろな考え方がある。
「あったらいいな」の関数として次のようなものを作るプログラム例を示す。

関数fgetcはファイルから1文字読み込む作業を行い,読み込めたら,1文字を返し,そうでなかったらEOFを返すものであった。
また関数fgetsはファイルから1行読み込む作業を行い,読み込めたら,その文字列を文字列領域に返し,そうでなかったら関数の値としてNULLを返すものであった。
そこで,これらに似た仕様の関数int fgetword(char buff[], int size, FILE *fp)があったらいいなの関数である。
この関数は,
ファイルから1単語読み込む作業を行い,
読み込めたら,その単語を文字列領域buffに返し,関数の値として1を返し,
そうでなかったら関数の値として0を返す
という動作を行う。
この関数を作るのは面倒だが,出来てしまえば,List.2のようにmainプログラムの流れが読みやすくなる。
(FILE *fpというのが出て来るが,後でポインタのところで学習する)

List 2 extractWord1.c

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

/*
ファイルfpから単語を読み出し,buffにしまって返す関数
char buff[] 見つかった単語をしまう文字列変数
int size    buffの大きさ
FILE *fp    ファイルストリーム
単語を読み出すことに成功したら1,そうでなかったら0を返す
*/
int fgetword(char buff[], int size, FILE *fp)
{
    static int oldch=0;      /*直前に読み込んだ文字*/
    int ch;                  /*読み込んだ文字*/
    int NowInWord=0; /*単語検出中は1,疑似単語検出中は-1,そうでない時は0*/
    int cnt=0; /*読み込んだ文字数カウンタ*/
    int foundWord=0; /*単語が見つかったら1,そうでなかったら0*/
    while (foundWord==0 && cnt<size-1 && (ch=fgetc(fp)) != EOF) {
        /*単語が見つかっておらず,かつ読み込み文字数がbuffの大きさより小さく,
        かつファイルから1文字読み込んでchに代入して,それがEOFでなかったらの意味。
        条件文は左側から順に評価され,継続条件が満たされていないことが
        分かった時点でそれ以上の評価は行われなくなる。
        (ch=fgetc(fp)) != EOFの評価を最初に行うとこのプログラムでは
        正しく動作することができない。なぜかは自分で考えてください。*/
        /* printf(isprint(ch)?"(%c)":"[%02x]",ch); */
        if (NowInWord==1) { /*単語読み込み中ならば*/
            if (isalnum(ch)||ch=='_') { /*chがアルファベット,数字または_だったら*/
                buff[cnt]=ch;
                cnt++;
            } else { /*単語の外に出たぞ*/
                NowInWord=0;
                foundWord=1;
            }
        } else if (NowInWord==-1) { /*疑似単語読み込み中ならば*/
            if (isalnum(ch)||ch=='_') { /*chがアルファベット,数字または_だったら*/
            } else { /*疑似単語の外に出たぞ*/
                NowInWord=0;
            }
        } else { /*単語読み込み中でないならば*/
            if (isalpha(ch)) { /*chがアルファベットだったら*/
                if (isdigit(oldch)) {
                    NowInWord=-1;
                } else {
                    NowInWord=1;
                    buff[cnt]=ch;
                    cnt++;
                }
            }
        }
        oldch=ch;
    }
    buff[cnt]='\0';
    return (0<cnt)?1:0;
}

int main()
{
    char fname[256]; /*ファイル名*/
    char buff[1024]; /*単語を受け取る文字列*/
    FILE *fp;        /*ファイルストリーム*/
    int count=0;     /*見つけた単語の数*/
    printf("file name : ");
    gets(fname);
   
    fp=fopen(fname,"r");
    if (fp==NULL) {
        printf("can't open %s\n",fname);
        exit(1);
    }
   
    while (fgetword(buff,1024,fp)==1) {
        /*ファイルから1単語読み込めたら*/
        printf("%s",buff); /*読み込んだ単語を表示する*/
        count++;
        if (count%8==0) { /*8回に一回改行したいなー*/
            printf("\n");
        } else {
            printf(" ");
        }
    }
    fclose(fp);
}

/*
対象としたtest.c
------------------------------------------------------
#include <stdio.h>

int main()
{
    printf("Hello. How are you?\n");
    printf("Fine, thanks. And You?\n");
    printf("So so.\n");
    return 0;
}
------------------------------------------------------

実行結果
file name : test.c
include stdio h int main printf Hello How
are you n printf Fine thanks And You
n printf So so n return



対象としたtest.txt
------------------------------------------------------
1234 ..wer qwe123+wer456
sw_qwe,q12_12,12qwe,A123B123
wsed_123

------------------------------------------------------

実行結果
file name : test.txt
wer qwe123 wer456 sw_qwe q12_12 A123B123 wsed_123


*/



関数int fgetword(char buff[], int size, FILE *fp)が使えるようになれば,ファイルから単語を抽出し,単語毎に出現回数を数えるといったプログラムも見通しよく作ることができる。
List.3では後で学ぶ構造体を使ってファイルから単語を抽出し,単語毎に出現回数を数えている。
データベース本体(
words_t words[1024])をmainで管理していて,各関数には引数でデータベース本体を見せている典型的なCの関数構造である。

List 3 extractWord2.c

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

typedef struct {
    char word[256]; /*登録単語*/
    int count;      /*出現回数*/
} words_t;

/*
ファイルfpから単語を読み出し,buffにしまって返す関数
char buff[] 見つかった単語をしまう文字列変数
int size    buffの大きさ
FILE *fp    ファイルストリーム
単語を読み出すことに成功したら1,そうでなかったら0を返す
*/
int fgetword(char buff[], int size, FILE *fp)
{
    static int oldch=0;      /*直前に読み込んだ文字*/
    int ch;                  /*読み込んだ文字*/
    int NowInWord=0; /*単語検出中は1,疑似単語検出中は-1,そうでない時は0*/
    int cnt=0; /*読み込んだ文字数カウンタ*/
    int foundWord=0; /*単語が見つかったら1,そうでなかったら0*/
    while (foundWord==0 && cnt<size-1 && (ch=fgetc(fp)) != EOF) {
        /*単語が見つかっておらず,かつ読み込み文字数がbuffの大きさより小さく,
        かつファイルから1文字読み込んでchに代入して,それがEOFでなかったらの意味。
        条件文は左側から順に評価され,継続条件が満たされていないことが
        分かった時点でそれ以上の評価は行われなくなる。
        (ch=fgetc(fp)) != EOFの評価を最初に行うとこのプログラムでは
        正しく動作することができない。なぜかは自分で考えてください。*/
        if (NowInWord==1) { /*単語読み込み中ならば*/
            if (isalnum(ch)||ch=='_') { /*chがアルファベット,数字または_だったら*/
                buff[cnt]=ch;
                cnt++;
            } else { /*単語の外に出たぞ*/
                NowInWord=0;
                foundWord=1;
            }
        } else if (NowInWord==-1) { /*疑似単語読み込み中ならば*/
            if (isalnum(ch)||ch=='_') { /*chがアルファベット,数字または_だったら*/
            } else { /*疑似単語の外に出たぞ*/
                NowInWord=0;
            }
        } else { /*単語読み込み中でないならば*/
            if (isalpha(ch)) { /*chがアルファベットだったら*/
                if (isdigit(oldch)) {
                    NowInWord=-1;
                } else {
                    NowInWord=1;
                    buff[cnt]=ch;
                    cnt++;
                }
            }
        }
        oldch=ch;
    }
    buff[cnt]='\0';
    return (0<cnt)?1:0;
}

/*********単語データベース(ここから)*********/

/*buffで与えられた単語をwordsに登録する。返す値はこれまで登録された単語の数*/
int registerWord(char buff[], words_t words[])
{
    static int nRegistered=0;     /*見つけた単語の数*/
    int found; /*未登録単語:-1 そうでなかったら見つかった番号*/
    int i;
    found=-1;
    i=0;
    while (i<nRegistered && found==-1) {
        if (strcmp(words[i].word,buff)==0) found=i;
        i++;
    }
    if (found==-1) { /*未登録済の単語だった*/
        strcpy(words[nRegistered].word, buff); /*後ろから前にコピー*/
        words[nRegistered].count=1;
        nRegistered++;
    } else { /*登録済の単語だった*/
        words[found].count++;
    }
    return nRegistered;
}

/*登録領域wordsのデータをNumWords個表示する*/
void printWords(words_t words[], int NumWords)
{
    int i;
    for (i=0; i<NumWords; i++) {
        printf("%3d %-16s : %3d\n",i,words[i].word,words[i].count);
    }
}

/*********単語データベース(ここまで)*********/

int main()
{
    words_t words[1024];   /*単語登録場所*/
    int nRegistered=0;     /*見つけた単語の数*/
    char fname[256]; /*ファイル名*/
    char buff[1024]; /*単語を受け取る文字列*/
    FILE *fp;        /*ファイルストリーム*/
    printf("file name : ");
    gets(fname);
   
    fp=fopen(fname,"r");
    if (fp==NULL) {
        printf("can't open %s\n",fname);
        exit(1);
    }
   
    while (fgetword(buff,1024,fp)==1) {
        /*ファイルから1単語読み込めたら         */
        /*読み込めた単語buffをデータベースに登録する*/
        nRegistered=registerWord(buff, words);
    }
    fclose(fp);
   
    printWords(words,nRegistered);
}

/*
対象としたtest.c
------------------------------------------------------
#include <stdio.h>

int main()
{
    printf("Hello. How are you?\n");
    printf("Fine, thanks. And You?\n");
    printf("So so.\n");
    return 0;
}
------------------------------------------------------

実行結果
file name : test.c
  0 include          :   1
  1 stdio            :   1
  2 h                :   1
  3 int              :   1
  4 main             :   1
  5 printf           :   3
  6 Hello            :   1
  7 How              :   1
  8 are              :   1
  9 you              :   1
 10 n                :   3
 11 Fine             :   1
 12 thanks           :   1
 13 And              :   1
 14 You              :   1
 15 So               :   1
 16 so               :   1
 17 return           :   1
*/





JAVAなどのオブジェクト志向言語ではデータベースを操作関数を含めた1つのオブジェクトとして捉える感覚になる。
データベースオブジェクトの操作に関する変数もデータベースオブジェクト内で完結する。
また変数fpもmainで管理する必要がなくなる。
JAVAのstaticクラスをイメージしたプログラムは次のようになる。

List 4 extractWord3.c

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

/*********ファイル読み込みシステム(ここから)*********/
FILE *fp=NULL;
/*
ファイル読み込み用オープンの関数
成功したら1,失敗したら0を返す
*/
int openFileToTextRead(char filename[]) {
    if (fp!=NULL) {
        printf("現在作業中なので,新たなファイルを開くことは出来ません\n");
        return 0;
    }
    fp=fopen(filename,"r");
    if (fp==NULL) {
        printf("can't open %s\n",filename);
        return 0;
    }
    return 1;
}

/*
ファイルから単語を読み出し,buffにしまって返す関数
char buff[] 見つかった単語をしまう文字列変数
int size    buffの大きさ
単語を読み出すことに成功したら1,そうでなかったら0を返す
*/
int fgetword(char buff[], int size)
{
    static int oldch=0;      /*直前に読み込んだ文字*/
    int ch;                  /*読み込んだ文字*/
    int NowInWord=0; /*単語検出中は1,疑似単語検出中は-1,そうでない時は0*/
    int cnt=0; /*読み込んだ文字数カウンタ*/
    int foundWord=0; /*単語が見つかったら1,そうでなかったら0*/
    while (foundWord==0 && cnt<size-1 && (ch=fgetc(fp)) != EOF) {
        /*単語が見つかっておらず,かつ読み込み文字数がbuffの大きさより小さく,
        かつファイルから1文字読み込んでchに代入して,それがEOFでなかったらの意味。
        条件文は左側から順に評価され,継続条件が満たされていないことが
        分かった時点でそれ以上の評価は行われなくなる。
        (ch=fgetc(fp)) != EOFの評価を最初に行うとこのプログラムでは
        正しく動作することができない。なぜかは自分で考えてください。*/
        if (NowInWord==1) { /*単語読み込み中ならば*/
            if (isalnum(ch)||ch=='_') { /*chがアルファベット,数字または_だったら*/
                buff[cnt]=ch;
                cnt++;
            } else { /*単語の外に出たぞ*/
                NowInWord=0;
                foundWord=1;
            }
        } else if (NowInWord==-1) { /*疑似単語読み込み中ならば*/
            if (isalnum(ch)||ch=='_') { /*chがアルファベット,数字または_だったら*/
            } else { /*疑似単語の外に出たぞ*/
                NowInWord=0;
            }
        } else { /*単語読み込み中でないならば*/
            if (isalpha(ch)) { /*chがアルファベットだったら*/
                if (isdigit(oldch)) {
                    NowInWord=-1;
                } else {
                    NowInWord=1;
                    buff[cnt]=ch;
                    cnt++;
                }
            }
        }
        oldch=ch;
    }
    if (foundWord==0) {/*ファイルの終わりに達したので後始末*/
        fclose(fp);
        fp=NULL;
        oldch=0;
    }
    buff[cnt]='\0';
    return (0<cnt)?1:0;
}
/*********ファイル読み込みシステム(ここまで)*********/

/*********単語データベース(ここから)*********/
typedef struct {
    char word[256]; /*登録単語*/
    int count;      /*出現回数*/
} words_t;

words_t words[1024];   /*単語登録場所*/
int nRegistered=0;     /*見つけた単語の数*/

/*buffで与えられた単語を登録する*/
void registerWord(char buff[])
{
    int found; /*未登録単語:-1 そうでなかったら見つかった番号*/
    int i;
    found=-1;
    i=0;
    while (i<nRegistered && found==-1) {
        if (strcmp(words[i].word,buff)==0) found=i;
        i++;
    }
    if (found==-1) { /*未登録済の単語だった*/
        strcpy(words[nRegistered].word, buff); /*後ろから前にコピー*/
        words[nRegistered].count=1;
        nRegistered++;
    } else { /*登録済の単語だった*/
        words[found].count++;
    }
}

void printWords()
{
    int i;
    for (i=0; i<nRegistered; i++) {
        printf("%3d %-16s : %3d\n",i,words[i].word,words[i].count);
    }
}

/*********単語データベース(ここまで)*********/

int main()
{
    char fname[256]; /*ファイル名*/
    char buff[1024]; /*単語を受け取る文字列*/
    int ret;
    printf("file name : ");
    gets(fname);
   
    ret=openFileToTextRead(fname);
    if (ret==0) exit(1);
   
    while (fgetword(buff,1024)==1) {
        /*ファイルから1単語読み込めたら         */
        /*読み込めた単語をデータベースに登録する*/
        registerWord(buff);
    }
   
    printWords();
}

/*
対象としたtest.c
------------------------------------------------------
#include <stdio.h>

int main()
{
    printf("Hello. How are you?\n");
    printf("Fine, thanks. And You?\n");
    printf("So so.\n");
    return 0;
}
------------------------------------------------------

実行結果
file name : test.c
  0 include          :   1
  1 stdio            :   1
  2 h                :   1
  3 int              :   1
  4 main             :   1
  5 printf           :   3
  6 Hello            :   1
  7 How              :   1
  8 are              :   1
  9 you              :   1
 10 n                :   3
 11 Fine             :   1
 12 thanks           :   1
 13 And              :   1
 14 You              :   1
 15 So               :   1
 16 so               :   1
 17 return           :   1
*/