C言語関係掲示板

過去ログ

No841 単語の出現頻度を求める。

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

単語の出現頻度を求める。
投稿者---侑子(2003/11/23 21:24:37)


はじめまして。課題ができなくて質問に来ました。
課題は、
「与えられたファイル(”Bridge.txt")に含まれる単語を求め、結果をファイルに出力する。単語と出現回数の区切りをタブとし、出現データをソートする」
という課題です。
私の考え方は(例としてThis is a pen.)
まず、1文字読み取ってそれが区切り文字かどうか判断します。
そして、区切り文字なら¥0を、違うならその文字をmojiという配列に入れます。これを繰り返して単語を作りました。
単語表は、構造体を使って、
a.tangoにmojiと同じ単語があれば、a.countに+1するようにし、なければa.tangoにmojiをコピーするようにしました。
ソートは、どうしたらいいかわかりませんでした。。
とりあえず、私の考えたプログラムを貼ります。。。

/* 単語を数える */

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

main(void)
{
	FILE *fp;
	int c;
	struct tangohyo
	{
		char tango[100];	/* 単語 */
		int count;	/* カウント */
	};
	struct tangohyo a,b;
	char moji[31];	/* 文字 */
	int i=0;
	int n=0;
	int j=0;
	int m=0;

	/* ファイルの読み込み */
	if((fp=fopen("Bridge.txt","r"))==NULL)
	{
		printf("File not open...\n");
		exit(1);
	}
	/* tango初期化 */
	for ( i = 0; i < 100; ++i )
	{
        a.tango[i] = '\0';
	}

	for(m=0;m<31;++m)
	{
		moji[m]='\0';
	}

	/* 判断 */
	while((c=fgetc(fp)) != EOF)
	{
		if(ispunct(c))       /* 区切り文字かどうか */
		{
			moji[j]='\0';
		}
		else
		{	
			if(isalpha(c))	/* cがアルファベットなら */
			{
				if(isupper(c))
				{
					tolower(c);
				}
			}
			moji[j]=c;
			j++;
		}


		if(strstr(a.tango,moji)!=NULL)
			a.count++;
		else
			(strcpy(a.tango,moji));
		n++;

		b=a;
		
	}
	fclose(fp);

/* ファイルに出力 */
	fp=fopen("tangohyo.txt","w");
	for(i=0;n<i;i++)
		{	
			printf("%s	%d\n",b[i].tango,	b[i].count);
			fprintf(fp,"%s	%d\n",b[i].tango,	b[i].count);
		}
	fclose(fp);
	
	return;
}


構造体がまだ理解できていないのでこれでいいのかよくわかりません。
私の考え方ではどういうプログラムにすればいいか教えてください。それから、これを関数化しなければならいのですが、どうするのがベストでしょうか?
よろしくお願いいたします。

No.10749

Re:スラング
投稿者---おでん(2003/11/24 01:15:04)


※ まず、ファイルの中は、ASCII文字のみでしょうか?

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

main(void)
{
    FILE *fp;
    int c;
    struct tangohyo
    {
        char tango[100];    /* 単語 */
        int count;    /* カウント */
    };
    struct tangohyo a,b; ←これだと構造体が2個しか出来ません
    char moji[31];    /* 文字 */ ←1単語は30文字以内ですか?
    int i=0;
    int n=0;
    int j=0;
    int m=0;

    /* ファイルの読み込み */
    if((fp=fopen("Bridge.txt","r"))==NULL)
    {
        printf("File not open...\n");
        exit(1);
    }
    /* tango初期化 */ ←初期化する必要がありますか? あるならカウンタも
    for ( i = 0; i < 100; ++i )
    {
        a.tango[i] = '\0';
    }

    for(m=0;m<31;++m) ←上と同じ
    {
        moji[m]='\0';
    }

    /* 判断 */
    while((c=fgetc(fp)) != EOF)
    {
        if(ispunct(c))       /* 区切り文字かどうか */ ←記号だけが区切りですか?
        {
            moji[j]='\0';
        }
        else
        {    
            if(isalpha(c))    /* cがアルファベットなら */
            {
                if(isupper(c))
                {
                    tolower(c); ←tolower()の使い方が違います。
                }
            }
            moji[j]=c;
            j++;
        }


        if(strstr(a.tango,moji)!=NULL) ←strstr()を使うと"abcdefg" == "cde"になってしまいますよ。
            a.count++;
        else
            (strcpy(a.tango,moji)); ←tangoを100取っているのに、mojiが31なのはなぜでしょう?
        n++;

        b=a; ←昔のbは何処へ行ってしまうのでしょうか?
        
    }
    fclose(fp);

/* ファイルに出力 */
    fp=fopen("tangohyo.txt","w");
    for(i=0;n<i;i++)
        {    
            printf("%s    %d\n",b[i].tango,    b[i].count);
            fprintf(fp,"%s    %d\n",b[i].tango,    b[i].count);
        }
    fclose(fp);
    
    return;
}

単語の出現数をカウントするには、重複しない全ての単語を覚えておかないとなりません。
ので、構造体の配列が必要になると思います。単語数が分からないとしたら動的にメモリを
確保するなどの手段が必要になります。
 1文字づつ読み込むか、行単位で読み込んで処理をするかは好みだっと思いますが行単位の
方が私は楽な気がします。
 main()で一行読み込み、一行ごとに処理する関数を呼び出す。などの処理が考えられます。
また、ソートに関しては構造体そのものよりもそのアドレスをソートした方がいいと思います。

ソート関連は、この掲示板の過去ログを検索すると山ほど出てきますから探してみてください。


No.10751

Re:単語の出現頻度を求める。(表題を間違えました)
投稿者---おでん(2003/11/24 01:43:24)


ソースのコンパイル結果です。

~/tst >gcc -Wall -c tango.c
tango.c:9: warning: return type defaults to `int'
tango.c: In function `main':
tango.c:44: error: stray '\241' in program
tango.c:44: error: stray '\241' in program
tango.c:44: error: stray '\241' in program
tango.c:44: error: stray '\241' in program
tango.c:44: error: stray '\241' in program
tango.c:44: error: stray '\241' in program
tango.c:44: error: stray '\241' in program
tango.c:44: error: stray '\241' in program
tango.c:44: error: stray '\241' in program
tango.c:44: error: stray '\241' in program
tango.c:44: error: stray '\241' in program
tango.c:44: error: stray '\241' in program
tango.c:44: error: stray '\241' in program
tango.c:44: error: stray '\241' in program
tango.c:77: error: subscripted value is neither array nor pointer
tango.c:77: error: subscripted value is neither array nor pointer
tango.c:78: error: subscripted value is neither array nor pointer
tango.c:78: error: subscripted value is neither array nor pointer
tango.c:82: warning: `return' with no value, in function returning non-void
tango.c:83:2: warning: no newline at end of file

→ /* 区切り文字かどうか */の前が全角スペースになっていた

~/tst >gcc -Wall -c tango.c
tango.c:9: warning: return type defaults to `int'
tango.c: In function `main':
tango.c:77: error: subscripted value is neither array nor pointer
tango.c:77: error: subscripted value is neither array nor pointer
tango.c:78: error: subscripted value is neither array nor pointer
tango.c:78: error: subscripted value is neither array nor pointer
tango.c:82: warning: `return' with no value, in function returning non-void
~/tst >


printf("%s%d\n",b[i].tango,b[i].count); ←'b'は配列ではない
fprintf(fp,"%s %d\n",b[i].tango,b[i].count);

これは、struct tangohyo a,b;の宣言を
struct tangohyo a,b[100];とかにすればOK
struct tangohyo
{
	char tango[100];	/* 単語 */
	int count;	/* カウント */
};
の宣言はたぶん、tango[100];で単語の領域を100確保するつもりだったのだと思いますが、
実際は100文字分の領域が確保されるだけです。
従って、struct tangohyo a,b[100];で100単語まで保存する領域を作ります。
その上で、未登録の単語が現れたら格納し、登録済みならカウンタを増やす
処理が必要です。

あと、main()は、int main(int argc, char *argv[])と言う宣言が通常必要です。
従って、return;もreturn(0);とかにします。

No.10754

Re:単語の出現頻度を求める。
投稿者---おでん(2003/11/24 02:49:16)


多少バグあり and ソートしていないです。

/* 単語を数える */

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

#define MAX_TNG 500
#define TNG_SIZ 30

typedef struct{
    char    tango[TNG_SIZ+1];    /* 単語 */
    int        count;                /* カウント */
} TangoHyo ;

enum{ NO,YES } ;

int WordCnt( TangoHyo * tp, int num, const char * ptr )
{
    int pos ;
    int find;
    char buf[TNG_SIZ+1], *p ;
    while( *ptr != '\0' ){
        while( *ptr != '\0' && !isalpha(*ptr)){
            ptr++ ;
        }
        if( *ptr == '\0' ){
            break ;
        }
        p= buf ;
        for( pos= 0 ; pos < TNG_SIZ && isalpha(*ptr) ; pos++ ){
            *p++ = *ptr++ ;
        }
        *p= '\0';
        find= NO ;
        for( pos= 0 ; pos < num ; pos++ ){
            if( strcmp(tp[pos].tango,buf) == 0 ){
                tp[pos].count++ ;
                find= YES ;
                break ;
            }
        }
        if( !find ){
            strcpy(tp[num].tango,buf);
            tp[num].count= 1 ;
            num++ ;
        }
    }

    return num ;
}

int main(void)
{
    FILE *fp;
    char line[1024];
    TangoHyo tangohyo[ MAX_TNG ];
    int num=0;
    int cnt ;

    /* ファイルの読み込み */
    if((fp=fopen("bridge.txt","r"))==NULL)
    {
        printf("File not open...\n");
        exit(1);
    }

    /* 判断 */
    while( fgets( line, sizeof line, fp ) != 0){
        num= WordCnt( tangohyo, num, line ) ;
        if( MAX_TNG < num ){
            break ;
        }
    }
    fclose(fp);

    for( cnt= 0 ; cnt < num ; cnt++ ){
            printf("%s\t%d\n",tangohyo[cnt].tango, tangohyo[cnt].count);
    }

    return 0;
}
結果→
How	1
to	26
build	2
Win	7
Dynamic	1
Loading	1
Library	1
DLL	15
from	9
existing	2
static	8
library	13
NOTE	1
To	1
perform	2
steps	2
below	3
you	25
ll	1
need	2
contemparary	1
dlltool	4
for	16
example	3
Mumit	2
Khan	2
・
・


No.10757

Re:単語の出現頻度を求める。
投稿者---侑子(2003/11/24 04:04:41)


何度も何度もありがとうございます。
おでんさんのプログラムは私にはちょっと難しいかも・・でも、がんばって解読します・・。
構造体の配列も間違って覚えてたみたいでした・・・・本当にありがとうございました。

No.10764

Re:単語の出現頻度を求める。(表題を間違えました)
投稿者---RAPT(2003/11/24 14:02:48)


ご存知かとは思いますが、一応補足。

以下、ANSI-Cの場合。

>main()は、int main(int argc, char *argv[])と言う宣言が通常必要です。
もしくは、int main() でもOK。

>従って、return;もreturn(0);とかにします。
return 0; でOK。
カッコは絶対必要ではない。
# return文は関数じゃないから。

No.10750

Re:単語の出現頻度を求める。
投稿者---あかま(2003/11/24 01:36:01)


かなり試行錯誤の様子が見て取れますね。
判定すべき場所を間違えてたり、関数の使い方を間違えたりしています。
混乱してきたらコメントをつけていくと変なところを見つけられるかも知れません。
ツッコミどころはおでんさんが詳しく書いてくださいましたので簡単に手直ししてみました。
多少バグ持ちですが。
#include<stdio.h>
#include<string.h>
#include<ctype.h>
#include<stdlib.h>

main(void)
{
	FILE *fp;
	int c;
	struct tangohyo
	{
		char tango[100];	/* 単語 */
		int count;	/* カウント */
	};
	struct tangohyo a[100];
	char moji[31];	/* 文字 */
	int i,n,j,m;

	/* ファイルの読み込み */
	if((fp=fopen("Bridge.txt","r"))==NULL)
	{
		printf("File not open...\n");
		exit(1);
	}

	/* 判断 */
	for(j=0,n=0;(c=fgetc(fp)) != EOF;)
	{
		if(isalpha(c))	/* cがアルファベットなら */
		{
			moji[j] = tolower(c);
			j++;
		}
		else{
			moji[j]='\0';
			for(m=0;m < n;m++){
				if(strcmp(a[m].tango,moji)==0){//登録されてたとき
					a[m].count++;
					break;
				}
			}
			if(m == n){//登録されてなかった時
				(strcpy(a[n].tango,moji));
				a[n].count = 1;
				n++;
			}
			j=0;
		}
		
	}
	fclose(fp);

/* ファイルに出力 */
	fp=fopen("tangohyo.txt","w");
	for(i=0;i < n;i++)
		{	
			printf("%s	%d\n",a[i].tango,a[i].count);
			fprintf(fp,"%s	%d\n",a[i].tango,a[i].count);
		}
	fclose(fp);
	
	return;


No.10758

Re:単語の出現頻度を求める。
投稿者---侑子(2003/11/24 04:20:17)


はい。
いろいろやっているうちに、混乱しちゃってしまいました・・。
私は、

for(m=0;m < n;m++)
			{
				if(strcmp(a[m].tango,moji)==0)
				{					/*登録されてたとき*/
					a[m].count++;
					break;
				}
			}
			if(m == n)
			{ 					/*登録されてなかった時*/
				(strcpy(a[n].tango,moji));
				a[n].count = 1;
				n++;
			}
			j=0;
		}
っていうところを一体どうしたらいい分からなかったんです。ありがとうございました。

それから・・・申し訳ないんですが、ソートについてですが
for(k=0;k<n-1;k++)
{
	min=a[k].tango;
	s=k;
	for(p=k+1;p<n;p++)
	{
		if(strcmp(a[p].tango,min)<0)
		{
			min=a[p];
			s=p;
		}
	}
	dummy=a[k];
	a[k]=a[s];
	a[s]=dummy;
}

っていうのを、上のソースの次に入れたんですがダメでした。どこがいけないんでしょうか?たぶん、これも関数の関係?とかが違う気がするんですけど・・・
どうなんでしょう?もう少し考えてみますが、もしよかったらちょっとしたことでもいいので教えてください。
何度もごめんなさい。
お願いします。

No.10760

Re:単語の出現頻度を求める。
投稿者---侑子(2003/11/24 06:05:40)


あ、あとそれから・・・・・
ただの質問なのですが、
実行したときに出現回数が72の単語があるんです。
これは・・空白でしょうか?もし空白なら空白はカウントしたくないんですが空白もカウントされちゃいます・・。
isalpha()(アルファベット)って空白も入るんですか???


No.10761

Re:単語の出現頻度を求める。
投稿者---あかま(2003/11/24 10:18:34)


>実行したときに出現回数が72の単語があるんです。
>これは・・空白でしょうか?もし空白なら空白はカウントしたくないんですが空白もカウントされちゃいます・・。
それが先に書いたバグの1つめです。
アルファベット以外の文字が2文字以上続くと'\0'のみの単語が作成されます。
それが72個カウントされるんですね。手っ取り早く直すには

if(m == n && strlen(moji) != 0){//登録されてなかった時

でいいと思います。

ソートは構造体の代入をしっかり理解してください。
struct tangohyo a[100],b;
と構造体があるときは
b = a[j];
で、構造体全要素の代入ができます。要素ごとにやりたいのなら
strcpy(b.tango,a[j].tango);//かならず文字列のコピーはこれ
b.count = a[j].count;//intのコピーは普通に代入
となります。
単純ソートを行うとこうなります。

for(i=0;i < n;i++){
	for(j=i;j < n;j++){
		if(strcmp(a[j].tango,a[i].tango) < 0){//文字列の比較
			b = a[j];//構造体同士の入れ替え。
			a[j] = a[i];
			a[i] = b;
		}
	}
}

で、最後にもひとつバグですが
読み込んだファイルの最後の文字がアルファベットの場合(空白とか改行なら大丈夫)、その単語がカウントされません。
あくまでアルファベット以外の単語がきたときに登録する仕様だからです。
回避方法としては、登録する部分を関数化してファイルの読み込みが終了したら、最後に1回呼び出せばいいでしょう。


No.10765

Re:単語の出現頻度を求める。
投稿者---RAPT(2003/11/24 14:05:15)


ソートは、qsort()を使えば楽かな。

No.10766

Re:単語の出現頻度を求める。
投稿者---RAPT(2003/11/24 15:29:29)


>ソートは、qsort()を使えば楽かな。

あかま(2003/11/24 01:36:01)さんのコードに追加するなら、
int fnCompare(const struct tangohyo* data1, const struct tangohyo* data2)
{
  return strcmp(data1->tango, data2->tango);
}
をmain()関数の前に記述し、

  qsort(a, n, sizeof a[0], fnCompare);
を/* ファイルに出力 */の前に追加してやれば、ソートできます。

# まぁ、自力でソート関数を書いたほうが勉強になると思いますが。


No.10775

Re:単語の出現頻度を求める。
投稿者---侑子(2003/11/24 21:43:38)


いろいろ調べて、qsort()があるって言うことはわかったんですがいまいち使い方が分からなかったので、本にも載ってた方法(自分でソートを作る)でプログラムを作ってみましたが、うまくいかなくて相談にきたのでした。
qsort()も使ってやってみようと思います。
ありがとうございました。

No.10774

ありがとうございました!!
投稿者---侑子(2003/11/24 21:40:03)


みなさん、ありがとうございました!!!

今までぼやーっとしていた、構造体を分かるようになりました!
皆さんのおかげでこれから、構造体をバンバン使えるようになりそうです。
本当にありがとうございました。