コラム
2025/08/29
プログラミングについて 第119回目
『作っておくと便利なちょっとしたプログラム その3』
今回も前回に続いてプログラムを作っていきましょう。
まずは前回までのプログラムです。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#define NEW(type,element) (type *)malloc(sizeof(type)*(element))
#define DELETE(addr) free(addr)
#define READ_BUF_SIZE 8192
#define PROC_MODE_ULINE 1
#define PROC_MODE_RLINE 2
int proc_mode=PROC_MODE_ULINE;
int disp_total=0;
int enclose =0;
char *fname=NULL;
FILE *fp=stdin;
char *new_strcat(char *string1,char *string2);
void main(argc,argv)
int argc;
char *argv[];
{
if(analize_argment(argc,argv)== -1)goto end;
if(open_source()== -1)goto end;
if(proc_mode==PROC_MODE_ULINE){
}
else{
process_rline();
}
close_source();
end:
exit(0);
}
analize_argment(argc,argv)
int argc;
char *argv[];
{
int i;
for(i=1;i<argc;i++){
if(stricmp(argv[i],"-u")==0)proc_mode=PROC_MODE_ULINE;
else if(stricmp(argv[i],"-r")==0)proc_mode=PROC_MODE_RLINE;
else if(stricmp(argv[i],"-n")==0)disp_total=1;
else if(stricmp(argv[i],"-c")==0)enclose =1;
else{
for(;i<argc;i++){
if(fname && fname[0])fname=new_strcat(fname," ");
fname=new_strcat(fname,argv[i]);
}
}
}
return 1;
}
char *new_strcat(char *string1,char *string2)
{
if(string1==NULL){
string1=NEW(char,strlen(string2)+1);
strcpy(string1,string2);
}
else{
int l;
l=strlen(string1)+strlen(string2)+1;
if(l>_msize(string1))string1=realloc(string1,l);
strcat(string1,string2);
}
return string1;
}
open_source()
{
if(fname){
fp=fopen(fname,"r");
if(fp==NULL){
printf("入力ファイル\n");
printf("File : %s\n",fname);
printf("のオープン時にエラーが発生しました。\n");
printf("%s\n",strerror(errno));
return -1;
}
}
return 1;
}
close_source()
{
if(fname)fclose(fp);
return 1;
}
display_text(total,text)
int total;
char *text;
{
if(enclose){
if(disp_total)printf("%4d (%s)\n",total,text);
else printf("(%s)\n",text);
}
else{
if(disp_total)printf("%4d %s\n",total,text);
else printf("%s\n",text);
}
return 1;
}
process_rline()
{
char data[READ_BUF_SIZE+1];
int ldata;
char *prev_text;
char * cur_text;
int is,ie,total;
prev_text=new_strcat(NULL,"");
cur_text=NULL;
total=0;
while(ldata=fread(data,1,READ_BUF_SIZE,fp)){
data[ldata]='\n';
for(is=ie=0;ie<=ldata;ie++){
if(data[ie]!='\n')continue;
data[ie]='\0';
cur_text=new_strcat(cur_text,data+is);
is=ie+1;
if(ie==ldata)break;
if(strcmp(prev_text,cur_text)!=0){
if(total)display_text(total,prev_text);
prev_text[0]='\0';
prev_text=new_strcat(prev_text,cur_text);
total=0;
}
total++;
cur_text[0]='\0';
}
}
if(total)display_text(total,prev_text);
DELETE(prev_text);
DELETE( cur_text);
return 1;
}
今回からユニーク行の表示機能を付け加えていきます。
まず次のようにメイン関数に proc_mode が PROC_MODE_ULINE のときの処理を追加します。
void main(argc,argv)
int argc;
char *argv[];
{
if(analize_argment(argc,argv)== -1)goto end;
if(open_source()== -1)goto end;
if(proc_mode==PROC_MODE_ULINE){
process_uline();
}
else{
process_rline();
}
close_source();
end:
exit(0);
}
関数 process_uline は、
process_uline()
{
char data[READ_BUF_SIZE+1];
int ldata;
char *text;
int is,ie;
text=NULL;
while(ldata=fread(data,1,READ_BUF_SIZE,fp)){
data[ldata]='\n';
for(is=ie=0;ie<=ldata;ie++){
if(data[ie]!='\n')continue;
data[ie]='\0';
text=new_strcat(text,data+is);
is=ie+1;
if(ie==ldata)break;
add_unique(text);
text[0]='\0';
}
}
DELETE(text);
display_unique();
return 1;
}
この関数の内容は前回説明した関数 process_rline と殆ど変りません(もっと簡単になっています)ので特に説明はしません。問題は各行の文字列を関数 add_unique で処理する部分と、処理の最後に関数 display_unique で表示する部分です。関数 add_uniqueは与えられた文字列と同じものが既にあるときにはその個数を1個増やし、ないときには文字列を記憶する動作をするようにします。関数 display_unique は関数 add_uniqueで処理された結果を表示するようにします。基本的にはこれだけの機能があれば目的は達成できます。
ユニークなデータを保持する構造体を、
struct unique_data_strct{
char *text;
int total;
struct unique_data_strct *next;
};
struct unique_data_strct *unique_data=NULL;
とし、リスト形式で表現することにします。この宣言と定義は関数外で行い、グローバルにしておきます。
関数 add_unique は次のようになります。
add_unique(text)
char *text;
{
/* udp は新規にユニークデータを追加するときのリスト
の最後のポインタを示す目的に使用します。udc はリ
ストの検索や追加するユニークデータのポインタを示
す目的に使用します。*/
struct unique_data_strct *udp,*udc;
/* リストを検索し、同一の文字列があるか否かを検査し
ます。*/
udp=NULL;
udc=unique_data;
while(udc){
/* 同一の文字列があったときにループを終了します。*/
if(strcmp(udc->text,text)==0)break;
udp=udc;
udc=udc->next;
}
/* udc がヌルでないときには同一の文字列が発見された
ので個数を1つ増やします。*/
if(udc){
udc->total++;
}
/* udc がヌルのときには同一の文字列が発見されなかっ
たので新規にリストに追加します。*/
else{
udc=NEW(struct unique_data_strct,1);
udc->text=NEW(char,strlen(text)+1);
strcpy(udc->text,text);
udc->total=1;
udc->next=NULL;
/* udp がヌルのときはリストには何もなく、それ以外の
ときにはリストの末尾を示していますので、それに従
ってリストに追加します。*/
if(udp==NULL)unique_data=udc;
else udp->next =udc;
}
return 1;
}
この関数はあまり難しくないと思いますので、じっくりと眺めて下さい。
そして関数 display_unique です。
display_unique()
{
struct unique_data_strct *udc;
udc=unique_data;
while(udc){
display_text(udc->total,udc->text);
udc=udc->next;
}
return 1;
}
この関数もとくに説明の必要はありません。display_text は連続する同一の文字列の行の削除で使用したものをそのまま利用しています。
一応はこれでできあがりました。試しに実行してみると思い通りに表示されますが、表示の順番がいまいちです。それ以上に大きなテキストファイルのときに時間がかかってしまうことが問題です。25万行程度のテキストファイルで試したところ表示を始めるまでに1分もかかってしまいました。原因は同一文字列の検索方法にあることは皆さんも推測できると思います。リスト形式ですので無駄な検索が多いのです。これでは実用にはなりません。
次回は実行速度を上げるための工夫をしましょう。
第119回 プログラミングについて『作っておくと便利なちょっとしたプログラム その3』

プログラミングについて 第119回目
『作っておくと便利なちょっとしたプログラム その3』
今回も前回に続いてプログラムを作っていきましょう。
まずは前回までのプログラムです。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#define NEW(type,element) (type *)malloc(sizeof(type)*(element))
#define DELETE(addr) free(addr)
#define READ_BUF_SIZE 8192
#define PROC_MODE_ULINE 1
#define PROC_MODE_RLINE 2
int proc_mode=PROC_MODE_ULINE;
int disp_total=0;
int enclose =0;
char *fname=NULL;
FILE *fp=stdin;
char *new_strcat(char *string1,char *string2);
void main(argc,argv)
int argc;
char *argv[];
{
if(analize_argment(argc,argv)== -1)goto end;
if(open_source()== -1)goto end;
if(proc_mode==PROC_MODE_ULINE){
}
else{
process_rline();
}
close_source();
end:
exit(0);
}
analize_argment(argc,argv)
int argc;
char *argv[];
{
int i;
for(i=1;i<argc;i++){
if(stricmp(argv[i],"-u")==0)proc_mode=PROC_MODE_ULINE;
else if(stricmp(argv[i],"-r")==0)proc_mode=PROC_MODE_RLINE;
else if(stricmp(argv[i],"-n")==0)disp_total=1;
else if(stricmp(argv[i],"-c")==0)enclose =1;
else{
for(;i<argc;i++){
if(fname && fname[0])fname=new_strcat(fname," ");
fname=new_strcat(fname,argv[i]);
}
}
}
return 1;
}
char *new_strcat(char *string1,char *string2)
{
if(string1==NULL){
string1=NEW(char,strlen(string2)+1);
strcpy(string1,string2);
}
else{
int l;
l=strlen(string1)+strlen(string2)+1;
if(l>_msize(string1))string1=realloc(string1,l);
strcat(string1,string2);
}
return string1;
}
open_source()
{
if(fname){
fp=fopen(fname,"r");
if(fp==NULL){
printf("入力ファイル\n");
printf("File : %s\n",fname);
printf("のオープン時にエラーが発生しました。\n");
printf("%s\n",strerror(errno));
return -1;
}
}
return 1;
}
close_source()
{
if(fname)fclose(fp);
return 1;
}
display_text(total,text)
int total;
char *text;
{
if(enclose){
if(disp_total)printf("%4d (%s)\n",total,text);
else printf("(%s)\n",text);
}
else{
if(disp_total)printf("%4d %s\n",total,text);
else printf("%s\n",text);
}
return 1;
}
process_rline()
{
char data[READ_BUF_SIZE+1];
int ldata;
char *prev_text;
char * cur_text;
int is,ie,total;
prev_text=new_strcat(NULL,"");
cur_text=NULL;
total=0;
while(ldata=fread(data,1,READ_BUF_SIZE,fp)){
data[ldata]='\n';
for(is=ie=0;ie<=ldata;ie++){
if(data[ie]!='\n')continue;
data[ie]='\0';
cur_text=new_strcat(cur_text,data+is);
is=ie+1;
if(ie==ldata)break;
if(strcmp(prev_text,cur_text)!=0){
if(total)display_text(total,prev_text);
prev_text[0]='\0';
prev_text=new_strcat(prev_text,cur_text);
total=0;
}
total++;
cur_text[0]='\0';
}
}
if(total)display_text(total,prev_text);
DELETE(prev_text);
DELETE( cur_text);
return 1;
}
今回からユニーク行の表示機能を付け加えていきます。
まず次のようにメイン関数に proc_mode が PROC_MODE_ULINE のときの処理を追加します。
void main(argc,argv)
int argc;
char *argv[];
{
if(analize_argment(argc,argv)== -1)goto end;
if(open_source()== -1)goto end;
if(proc_mode==PROC_MODE_ULINE){
process_uline();
}
else{
process_rline();
}
close_source();
end:
exit(0);
}
関数 process_uline は、
process_uline()
{
char data[READ_BUF_SIZE+1];
int ldata;
char *text;
int is,ie;
text=NULL;
while(ldata=fread(data,1,READ_BUF_SIZE,fp)){
data[ldata]='\n';
for(is=ie=0;ie<=ldata;ie++){
if(data[ie]!='\n')continue;
data[ie]='\0';
text=new_strcat(text,data+is);
is=ie+1;
if(ie==ldata)break;
add_unique(text);
text[0]='\0';
}
}
DELETE(text);
display_unique();
return 1;
}
この関数の内容は前回説明した関数 process_rline と殆ど変りません(もっと簡単になっています)ので特に説明はしません。問題は各行の文字列を関数 add_unique で処理する部分と、処理の最後に関数 display_unique で表示する部分です。関数 add_uniqueは与えられた文字列と同じものが既にあるときにはその個数を1個増やし、ないときには文字列を記憶する動作をするようにします。関数 display_unique は関数 add_uniqueで処理された結果を表示するようにします。基本的にはこれだけの機能があれば目的は達成できます。
ユニークなデータを保持する構造体を、
struct unique_data_strct{
char *text;
int total;
struct unique_data_strct *next;
};
struct unique_data_strct *unique_data=NULL;
とし、リスト形式で表現することにします。この宣言と定義は関数外で行い、グローバルにしておきます。
関数 add_unique は次のようになります。
add_unique(text)
char *text;
{
/* udp は新規にユニークデータを追加するときのリスト
の最後のポインタを示す目的に使用します。udc はリ
ストの検索や追加するユニークデータのポインタを示
す目的に使用します。*/
struct unique_data_strct *udp,*udc;
/* リストを検索し、同一の文字列があるか否かを検査し
ます。*/
udp=NULL;
udc=unique_data;
while(udc){
/* 同一の文字列があったときにループを終了します。*/
if(strcmp(udc->text,text)==0)break;
udp=udc;
udc=udc->next;
}
/* udc がヌルでないときには同一の文字列が発見された
ので個数を1つ増やします。*/
if(udc){
udc->total++;
}
/* udc がヌルのときには同一の文字列が発見されなかっ
たので新規にリストに追加します。*/
else{
udc=NEW(struct unique_data_strct,1);
udc->text=NEW(char,strlen(text)+1);
strcpy(udc->text,text);
udc->total=1;
udc->next=NULL;
/* udp がヌルのときはリストには何もなく、それ以外の
ときにはリストの末尾を示していますので、それに従
ってリストに追加します。*/
if(udp==NULL)unique_data=udc;
else udp->next =udc;
}
return 1;
}
この関数はあまり難しくないと思いますので、じっくりと眺めて下さい。
そして関数 display_unique です。
display_unique()
{
struct unique_data_strct *udc;
udc=unique_data;
while(udc){
display_text(udc->total,udc->text);
udc=udc->next;
}
return 1;
}
この関数もとくに説明の必要はありません。display_text は連続する同一の文字列の行の削除で使用したものをそのまま利用しています。
一応はこれでできあがりました。試しに実行してみると思い通りに表示されますが、表示の順番がいまいちです。それ以上に大きなテキストファイルのときに時間がかかってしまうことが問題です。25万行程度のテキストファイルで試したところ表示を始めるまでに1分もかかってしまいました。原因は同一文字列の検索方法にあることは皆さんも推測できると思います。リスト形式ですので無駄な検索が多いのです。これでは実用にはなりません。
次回は実行速度を上げるための工夫をしましょう。