7 文字列照合
文字列照合のアルゴリズムとして,単純照合法およびKMP法を実装する.
ソースコードは,次のURLからダウンロードできる.
7.1 単純照合法
readFile.c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
/**
* ファイルを読み込みむ.
* @param path 読み込むファイルのパス
* @param str 読み込んだ文字列を格納する配列へのポインタ
* @return 読み込んだファイルのサイズ(バイト数).
*/
int readFile(char* path, char* str) {
FILE* fp = NULL;
fp = fopen(path, "r");
if ((fp = fopen(path, "r")) == NULL) {
perror("Cannot not open file");
exit(1);
}
int len = 0;
while (!feof(fp))
str[len++] = (char)fgetc(fp);
fclose(fp);
str[--len] = '\0'; // 末尾のEOFを削除
return len;
}
cmp.c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
extern bool isVerbose;
extern int Ncmp;
/**
* 文字比較関数.比較回数をカウントする.
* @param a 比較文字
* @param b 比較文字
* @return a と b が等しければ true, 等しくなければ false.
*/
bool cmp(char a, char b) {
if (isVerbose)
fprintf(stderr, "cmp(%c, %c)\n", a, b);
Ncmp++;
if (a == b)
return true;
else
return false;
}
mainNaive.c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#define PAT_MAX 256 // パターンの最大の長さ
#define TEXT_MAX 1024 * 1024 * 10 // テキストの最大の長さ(10 MB)
bool isVerbose = false; // 比較回数表示スイッチ
unsigned int Ncmp = 0; // 比較回数のカウンタ
int readFile(char*, char*);
bool cmp(char, char);
/**
* 単純照合法による文字列照合.
* @param text テキスト
* @param pat 検索パターン
* @return 照合に成功した位置.失敗した場合は -1.
*/
int naive(char* text, unsigned int textlen, char* pat, unsigned int patlen) {
// コードを完成させる.
}
/**
* メイン関数
* @param argc
* @param argv
* @return 終了ステータス:正常終了 0 / それ以外 1
*/
int main(int argc, char** argv) {
char pat[PAT_MAX]; // 検索パターン
char* text = (char *)malloc(TEXT_MAX * sizeof(char));
int patlen = 0;
int textlen = 0;
// 引数処理・ファイル読み込み
if (argc < 3 || 4 < argc) {
fprintf(stderr, "Usage: ./main [-v] <text file> <pattern file>\n");
return 1;
}
int i = 1;
if (strcmp(argv[i], "-v") == 0) {
isVerbose = true;
i++;
}
textlen = readFile(argv[i++], text);
patlen = readFile(argv[i], pat);
// pat 末尾の改行コードを削除
while (pat[patlen - 1] == '\r' || pat[patlen - 1] == '\n')
pat[--patlen] = '\0';
// 文字列照合・結果表示
if (isVerbose) {
printf("text size: %d\n", textlen);
printf("pattern size: %d\n", patlen);
}
printf("Pattern found at %d.\n", naive(text, textlen, pat, patlen));
if (isVerbose)
printf("# of comparison(s): %d.\n", Ncmp);
free(text);
return 0;
}
Makefile
#CC = gcc
#CFLAGS = -g
all: mainNaive mainKMP
mainNaive: cmp.o readFile.o mainNaive.o
mainKMP: cmp.o readFile.o mainKMP.o
clean:
rm -f *.o mainNaive mainKMP
コンパイルと実行
$ make mainNaive
cc -c -o mainNaive.o mainNaive.c
cc -c -o cmp.o cmp.c
cc -c -o readFile.o readFile.c
cc mainNaive.o cmp.o readFile.o -o mainNaive
$ echo This is a pen. > text
$ echo pen > pat
$ ./mainNaive text pat
Pattern found at 10.
パターンが存在しない場合は -1 を表示する.
$ echo ix > pat
$ ./mainNaive text pat
Pattern found at -1.
また,"-v" オプションとともに実行すると,比較の詳細と比較回数を表示する.
$ ./mainNaive -v text pat
text size: 15
pattern size: 2
cmp(i, T)
cmp(i, h)
(略)
Pattern found at -1.
# of comparisons: 17.
プログラムの説明
- C言語では,文字列は char 型の配列として扱い,文字列の終端はナル文字 '\0' で識別される.
- ファイルから文字列を読み出す場合,改行コードも文字として扱われることに注意すること.改行コードは OS によって異なるが,macOS や Linux では '\n' が用いられる.今回のサンプルコードでは,検索パターンの末尾の改行コードは削除するようになっている.
- 文字の比較回数を調査するために,cmp()関数を用意している.各文字の比較の際にこの関数を呼び出すと,比較回数を格納するカウンターが加算される.また,"-v" オプションを指定することで,比較回数の表示を簡単に行うことができる.
基本課題1
- 単純照合法に基づいて,上記のコードにおけるnaive()を作成してプログラムを完成させなさい.
- 作成したプログラムを簡単な動作例を用いて説明し,それが正しく動作することを示しなさい.
7.2 KMP法
mainKMP.c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#define PAT_MAX 256 // パターンの最大の長さ
#define TEXT_MAX 1024 * 1024 * 10 // テキストの最大の長さ(10 MB)
bool isVerbose = false; // 比較回数表示スイッチ
unsigned int Ncmp = 0; // 比較回数のカウンタ
int readFile(char*, char*);
bool cmp(char, char);
/**
* next配列の計算
* @param pat パターン配列
* @param next next配列
* @return なし
*/
void compnext(char* pat, unsigned int* next) {
// 関数を完成させる.
return;
}
/**
* KMP法による文字列照合.
* @param text テキスト
* @param pat 検索パターン
* @return 照合に成功した位置.失敗した場合は -1.
*/
int kmp(char* text, unsigned int textlen, char* pat, unsigned int patlen) {
unsigned int next[PAT_MAX]; // NEXT配列
// 関数を完成させる
}
/**
* メイン関数
* @param argc
* @param argv
* @return 終了ステータス:正常終了 0 / それ以外 1
*/
int main(int argc, char** argv) {
// mainNaive.c と同様なので省略.
}
基本課題2
- compnext関数を完成させなさい.
- 作成したcompnext関数が正しく動,KMP法によって文字列照合を行うプログラムを作成しなさい.
- KMP法に基づいて,上記のコードにおけるkmp()を作成してプログラムを完成させなさい.
- 作成したプログラムを簡単な動作例を用いて説明し,それが正しく動作することを示しなさい.
7.3 発展課題:文字列照合アルゴリズムの比較
発展課題1
長さnの文字列に対して,長さmのパターンを照合することを考える.
- 異なる長さの文字列に対して,単純照合法とKMP法の比較回数を実際に調査し,どちらが高速か検討しなさい.
- 調査した比較回数のデータは散布図などを使って可視化すると良い.散布図の作成には,gnuplot や Python(Matplotlib)などが利用できる.
- サイズの大きなテキストとしては,Linuxに付属している辞書ファイルなどを利用すると良い.
- www.coins.tsukuba.ac.jp: /usr/share/dict/words
発展課題2
- 単純照合法で最悪となる文字列およびパターンはどのようなものか.実例を挙げるとともに,計算量をオーダで示しなさい.
- さらに単純照合法で最悪となる文字列およびパターンに対してKMP法における比較回数を調査し,どちらが高速かを検討しなさい.
7.4 提出ファイルの構成
- ディレクトリ
class7
を作成して,その中に上記ファイルを配置すること. class7
を ZIP で圧縮したclass7.zip
ファイルを manaba に提出すること.
[class7.zip)の構造]
class7/
+- report7.pdf
+- mainKMP.c*
+- mainNaive.c
+- cmp.c // 配布ファイルと同一
+- readFile.c // 配布ファイルと同一
+- Makefile // 配布ファイルと同一
7.5 提出締切
2025年1月27日(月)13:30