文字列照合

文字列照合のアルゴリズムとして,単純照合法およびKMP法を実装する.

プログラム:単純照合法

以下のソースコードは,次のURLからダウンロードできる. * ソースコード: https://www.coins.tsukuba.ac.jp/~amagasa/lecture/dsa-files/dsa-code-string.zip

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;
}

naive.c

#include <stdlib.h>
#include <stdbool.h>

extern bool isVerbose;
extern int Ncmp;

bool cmp(char, char);

/**
 * 単純照合法による文字列照合.
 * @param text テキスト
 * @param pat 検索パターン
 * @return 照合に成功した位置.失敗した場合は -1.
 */
int naive(char* text, unsigned int textlen, char* pat, unsigned int patlen) {
    /* 関数を完成させなさい */
}

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*);
int naive(char*, int, char*, int);

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 comparisons: %d.\n", Ncmp);

  free(text);

  return 0;
}

Makefile

mainNaive: cmp.o naive.o readFile.o

使い方

これはテキストおよび(検索)パターンを含む二つのファイルを引数として与えると,単純照合法による文字列照合を行い,パターンの含まれる位置を表示するプログラムでである.

$ 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()を作成してプログラムを完成させなさい. 作成したプログラムを簡単な動作例を用いて説明し,それが正しく動作することを示しなさい.

基本課題2

上記ソースコードに基づいて,KMP法によって文字列照合を行うプログラムを作成する. まず,パターン pat を解析してまくらパターンのサイズを格納する配列 next を計算する関数

void compnext(char* pat, int* next)

を含むソースファイル compnext.c を作成しなさい. さらに,作成した関数が正しく動作することを,例を使って示しなさい.

基本課題3

KMP法によって文字列照合を行うプログラム kmp.c を完成させなさい. このために修正したメイン関数は mainKMP.c とする. さらに,作成したプログラムを簡単な動作例を用いて説明し,それが正しく動作することを示しなさい.

発展課題:文字列照合アルゴリズムの比較

発展課題1

長さnの文字列に対して,長さmのパターンを照合することを考える. 異なる長さの文字列に対して,単純照合法とKMP法の比較回数を調査し,どちらが高速か検討しなさい.

ヒント

調査したデータは gnuplot や Python(Matplotlib)などを使って,分かりやすくまとめること. 参考 *「二次元散布図」を参照 http://data-science.gr.jp/implementation/ida_gnuplot_basic_usage.html

発展課題2

単純照合法で最悪となる文字列およびパターンはどのようなものか.実例を挙げるとともに,計算量をオーダで示しなさい. さらに,単純照合法で最悪となる文字列およびパターンに対してKMP法における比較回数を調査し,どちらが高速かを検討しなさい.

提出締切

2023年1月30日(月)13:30