C言語関係掲示板

過去ログ

No.320.正規表現 grep -x と同じ動作

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

お願いします!!!
投稿者---ゆっきー(2002/07/07 02:25:35)


 あるファイルから、与えられた正規表現との照合が成功した
行のみを出力するプログラムmygrepを作成せよ。(grep -xと
同じ動作)

 ・呼び出し形式は、次のような1引数または2引数とする。
    (両方の形式で実行できること。)

     mygrep 正規表現 ファイル名
     mygrep 正規表現
   
   後者の場合は、照合対象となる文字列は、標準入力から
  与えられるものとする。

  ・扱える正規表現は、次のような簡略化(制限)したもの
  でよい。

     1.文字集合は、S={a,b,c,...,z}とする。
     2.a*のように記述する。
     3.'+'の代わりに、'|'という記号を用いる。 
     4.任意の文字は、記号'.'で表現する。つまり、
          . = (a|b|.....|z)
     5.'*'は、a-zの文字、あるいは、'.'にしか付加
      されない。
        a*  .*   ((ab|c)*などは考えない)
     6.'|'の引数は2個のみとし、引数は'*'や'|'を
      含まない。
        (ab|c).*k ((ab|c*), (ab(cd|ef))などは
                考えない)
 
という課題が出されました。正規表現を分割するというところまではなんとかできたのですが、正規表現をオートマトンに変換するのがどうしても思いつきません。どうか力をおかしください。 


No.2050

Re:お願いします!!!
投稿者---かずま(2002/07/11 13:39:20)


「お願いします!!!」についての質問ですか。
「お願いします!!!」を教えて欲しいのですか。
「お願いします!!!」がわからないのですか。
「お願いします!!!」は、題名として不適切です。
「正規表現との照合」とでもすれば十分なのに、残念です。

> 正規表現を分割するというところまではなんとかできたのですが、
> 正規表現をオートマトンに変換するのがどうしても思いつきません。

その出来たところまでを載せてくれたら、私も興味を持って、もっと早くコメントを書いたかも
しれません。

さて、正規表現を木構造にするところまでは、すぐに出来るんですが、オートマトンに変換する
ところは、すぐには思いつきませんでした。昔、egrep のソースを読んで、決定性有限オートマト
ンの状態遷移表を作るコードには感心した覚えはあるのですが、結構複雑だったように思います。

で、今回の場合、かなり制限された正規表現ですから、正規表現のまま、パターンマッチングを
行えば、わりと簡単に済むんじゃないかと考えて、次のようなコードをでっち上げました。
質問者の期待に答えるものではありませんが、まあこんなことでも実現できますよということで
ご容赦ください。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void error(void) { puts("regular expression error"); exit(1); }
int ischar(int c) { return c >= 'a' && c <= 'z' || c == '.'; }
int eqchar(int op, int c) { return (op == '.') ? (c >= 'a' && c <= 'z') : (op == c); }
int regex(const char *re, const char *s);

int or(const char *re, const char *s)
{
    const char *p1, *p2;  int n1, n2;

    p1 = strchr(re, '|');
    if (p1 == NULL) error();
    p2 = strchr(re, ')');
    if (p2 == NULL || p2 < p1) error();
    n1 = p1 - re;
    n2 = p2 - p1 - 1;
    if (strncmp(re, s, n1) == 0) return regex(p2 + 1, s + n1);
    if (strncmp(p1 + 1, s, n2) == 0) return regex(p2 + 1, s + n2);
    return 0;
}

int star(int op, const char *re, const char *s)
{
    for (;;) {
        if (regex(re, s)) return 1;
        if (!eqchar(op, *s++)) return 0;
    }
}

int regex(const char *re, const char *s)
{
    for (;;) {
        int op = *re++;
        if (op == 0)    return *s == '\0';
        if (op == '(')  return or(re, s);
        if (!ischar(op)) error();
        if (*re == '*')  return star(op, ++re, s);
        if (!eqchar(op, *s++)) return 0;
    }
}

void grep(FILE *fp, const char *re)
{
    char buf[1024], *p;

    while (fgets(buf, sizeof buf, fp)) {
        if (p = strchr(buf, '\n')) *p = '\0';
        if (regex(re,  buf)) puts(buf);
    }
}

int main(int argc, char **argv)
{
    switch (argc) {
      default: return printf("usage: mygrep pattern [file]\n"), 1;
      case 2:  grep(stdin, argv[1]); break;
      case 3:  {
            FILE *fp = fopen(argv[2], "r");
            if (fp == NULL) return printf("can't open %s\n", argv[2]), 1;
            grep(fp, argv[1]);
            fclose(fp);
      }
    }
    return 0;
}


No.2051

Re:お願いします!!!
投稿者---かずま(2002/07/11 14:26:52)


バグがありました。
  if (strncmp(re, s, n1) == 0) return regex(p2 + 1, s + n1);

  if (strncmp(re, s, n1) == 0 && regex(p2 + 1, s + n1)) return 1;
に訂正します。
たとえば、(ab|a)b という正規表現にマッチする文字列は、abb と ab ですが、
元のコードだと、ab がマッチしません。

No.2059

Re:お願いします!!!
投稿者---かずま(2002/07/12 11:23:09)


> 6.'|'の引数は2個のみとし、引数は'*'や'|'を含まない。
>   (ab|c).*k   ((ab|c*), (ab(cd|ef))などは考えない)

とありますが、'.' を含まないという条件はないんですね。
(a.|b) などが許されるように修正します。
    if (strncmp(re, s, n1) == 0 && regex(p2 + 1, s + n1)) return 1;
    if (strncmp(p1 + 1, s, n2) == 0) return regex(p2 + 1, s + n2);
を
    if (eqnstr(re, s, n1) && regex(p2 + 1, s + n1)) return 1;
    if (eqnstr(p1 + 1, s, n2)) return regex(p2 + 1, s + n2);

に変更し、次の関数を追加してください。

int eqnstr(const char *re, const char *s, int n)
{
    int i;

    for (i = 0; i < n; i++)
        if (!eqchar(re[i], s[i])) return 0;
    return 1;
}