このドキュメントは http://edu.net.c.dendai.ac.jp/ 上で公開されています。
今週からは、入力データの意味や内容を解析するために、文法理論を学びます。 参考書を選ぶ時は、「コンパイラ論」のようなものが参考になります。
コンピュータで記号列を解釈するには、その記号列がどのようなルールで作ら れているかを知る必要があります。 記号列を生成するルールを文法と言います。 そして、生成される記号列すべての集合を言語と言います。 文法を記号 G で表す時、その文法で生成される言語を L(G) と書くことがあ ります。 なお、記号の集合をアルファベット と言います。 しばしばアルファベットはΣで表されます。 そして、そのアルファベットΣから作られる記号列全体の集合を で表します。 なお長さが0の記号列(空列)をεで表します。
正規表現を次のように定義します。
正規表現は一つの文法になりうるので、正規表現で一つの言語を定義すること ができます。
メモリーが有限個しか使えないコンピュータが計算できる言語は正規表現だけ であることが知られています。 このように正規表現はプログラムと非常に密接な関係にありますので、さまざ まなところで使用されています。 例えば、 MS-DOS プロンプトやコマンドプロンプトなどでも、ファイル名を指示 する時、不完全ながらも正規表現が使えます。
但し、これは ab, abab などの表現を指定できないので、完全な意味での正規 表現ではありませんが、一応複数のファイル名などを一つの文法で表すことが できます。
コマンドプロンプトまたは MS-DOS プロンプトで c:\windows または c:\winnt ディレクトリの中のファイルのうち、次の条件に当てはまるファイ ル名を dir コマンドを使って表示しなさい。 但し、 Windows や MS-DOS はファイル名を二つの文字列を.(ピリオド)でつな がった形で表し、後ろの文字列を拡張子と言います。 例えば「拡張子が txt のファイル名」は dir *.txt で表示します。
Java ではバージョン 1.4 から java.util.regex パッケージが追加され、次 のような構文で正規表現が使えるようになりました。
Pattern p = Pattern.compile("a*b");
Matcher m = p.matcher("aaaaab");
boolean b = m.matches();
また簡易版として次の表記も可能になりました。
boolean b = "aaaaab".matches("a*b");
以下は Java での正規表現を表しかたの例です(詳しくは言語のリファレンス を御覧下さい)。
\p{名前}
は POSIX character classes を指定するのに使います。
\p{Lower}
は小文字[a-z]、\p{Upper}
は大文字
[A-Z]、\p{Alpha}
はアルファベット[\p{Lower}\p{Upper}]、
\p{Digit}
は数字[0-9]、\p{Alnum}
は英数字
[\p{Alpha}\p{Digit}]、\p{Space}
は空白[ \t\n\x0B\f\r] を表
します。
以下のアプレットを使って、ランダムな 1000 個の文字列のうち、次の文字列 がいくつ出て来るか調べなさい。但し、文字列の長さは 5 とします。
次の正規表現が受理する言語(文字列の集合)を求めなさい。
次の文字列を表す正規表現を示しなさい。
例えば、単語は、英字で始まって、英字または数字が連続するものですが、そ
れは [A-Za-z][A-Za-z0-9]*
と書くことができます。あるいは
Java ではもっと簡単に \p{Alpha}\p{Alnum}*
と書くこともで
きます。
これを使うと次のようなプログラムが書けます。
import java.util.regex.*;
class Sample {
public static void main(String[] args){
String sample="This is a pen.";
Pattern p = Pattern.compile("[A-Za-z][A-Za-z0-9]*");
Matcher m = p.matcher(sample);
while(m.find()){
System.out.println(m.group());
}
}
}
String input="-10+30-20";
C や C++ はそのままでは正規表現を取り扱えません。 UNIX 系の OS では POSIX 規格の関数が用意されているため利用できます (Windows 2000 Professional や Windows XP 系は POSIX をうたってますが、 このライブラリはないようです)。 C では regex.h に regcomp という正規表現のコンパイラと regexec という 正規表現を文字列と比較してマッチする位置を格納する関数があります。 以下は上記の Java とほぼ同等のプログラム例です。
#include <sys/types.h>
#include <regex.h>
#include <stdio.h>
int main(void){
char sample[]="This is a pen.";
char pattern[]="[A-Za-z][A-Za-z0-9]*";
regex_t m;
size_t nmatch=1;
regmatch_t pmatch[nmatch];
int i;
char *p;
regcomp(&m,pattern,0);
for(p=sample;!regexec(&m,p,nmatch,pmatch,0);p+=pmatch[0].rm_eo){
for(i=pmatch[0].rm_so;i<pmatch[0].rm_eo; i++){
printf("%c",p[i]);
}
printf("\n");
}
return 0;
}
なお、 mingw でも rx というライブラリをインストールすれば上のプログラ
ムを利用できます。
その場合、
#include <regex.h>
を
#include <rxposix.h>
と書き換えた後、
コンパイルする時に gcc ファイル名 -lrxとすれば実行ファイル
を作れます。
C++ 言語の拡張として Boost テンプレートライブラリがあります。これは C++ の正式な言語拡張ではありませんが、次期の C++ にライブラリの多くが 含まれることが策定されています。 Boost の中に xpressive または regex という str::string 用の正規表現処 理テンプレートが存在します。 使用法に関しては詳しく説明しませんが、レポートなどで使用しても構いませ ん。
lex や flex という、「正規表現からそれを認識する C プログラ ムを生成する」プログラムがあります。 構文は次のようになっています。
初期設定
%%
正規表現パターン1 {処理プログラム1}
正規表現パターン2 {処理プログラム2}
...
正規表現パターンn {処理プログラムn}
%%
C プログラム
入力は基本的には標準入力になります。そして、
処理プログラムではマッチした文字列へのポインタが yytext という変数名で
参照できます。
また、マッチしたもの以外を無視するには「その他は無視する」というパター
ン . ;
を付加する必要があります。
上記のJava や C のプログラム同様に名前を認識するプログラムを示します。 始めに標準入力から入力を得る例を示します。
%% [A-Za-z][A-Za-z0-9]* { printf("%s\n",yytext);} . ; %% int main(void){ yylex(); return 0; }
これを sampreg.l というファイルに保存した場合、
%{ #undef YY_INPUT #define YY_INPUT(b, r, ms) (r=my_input(b,ms)) %} %% [A-Za-z][A-Za-z0-9]* { printf("%s\n",yytext);} . ; %% char samp[]="This is a pen."; char* ptr; extern int my_input(); int my_input(char *buf, int max_size) { int n; n = strlen(ptr); if(n>max_size){ n=max_size; } if (n>0){ strncpy(buf, ptr, n); ptr +=n; } return n; } int main(void){ ptr=samp; yylex(); return 0; }
char inputstring[]="-10+30-20";
ここまでは正規表現を解釈するツールを利用してきました。 これからは、正規表現をプログラムに変換する手法を学びます。 単純な正規表現ならわざわざツールに頼る必要はありません。
始めにオートマトンについて学びます。 オートマトンとは次のような有向グラフです。
文字列に対して、開始ノードから一文字ずつたどって終了ノードにたどり着け た時、オートマトンはその文字列を受理すると言います。 一方、辺がなかったり、文字列が終った時に終了ノードではなかった時 拒否すると言います。 オートマトンにより、受理する文字列の集合が決定できます。受理する文字列 の集合をそのオートマトンが受理する言語と言います。
各ノードから出る辺に割り当てられている文字がすべて異なるオートマトンを 決定性オートマトンと言います。 このオートマトンは状態遷移図とも呼びます。 決定性オートマトンは入力により流れが分岐するだけですので、簡単にプログ ラムに変換できます。
一方、各ノードから出る辺に割り当てられている文字にダブりがあったり、 ε(空文字)が割り当てられているオートマトンを非決定性オート マトン と言います。 ここでは非決定性オートマトンの受理する言語と正規文法で定められる言語が 一致することを示します。 具体的には正規文法から非決定 性オートマトンへの変換の仕方を示します。 (非決定性オートマトンから正規文法への変換方法は省略します)
帰納法により証明します。
次の正規表現を受理する非決定性オートマトンを作りなさい。
package regtest;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class Regtest extends JApplet {
Container contentPane;
ResultPanel resultPanel;
ControlPanel controlPanel;
StringGenerator stringGenerator;
public void init(){
contentPane = getContentPane();
resultPanel = new ResultPanel();
contentPane.add(resultPanel.getPanel(),BorderLayout.NORTH);
controlPanel = new ControlPanel();
contentPane.add(controlPanel.getMiddlePanel(),BorderLayout.CENTER);
contentPane.add(controlPanel.getSliderPanel(),BorderLayout.SOUTH);
stringGenerator = new StringGenerator(controlPanel, resultPanel);
controlPanel.setStartButtonAction(stringGenerator.getStartAction());
controlPanel.setClearButtonAction(resultPanel.getClearAction());
}
}
class ResultPanel {
JPanel resultPanel;
ResultArea okArea, ngArea;
ClearAction clearAction;
ResultPanel(){
resultPanel=new JPanel();
okArea = new ResultArea("適合");
ngArea = new ResultArea("不適合");
resultPanel.add(okArea.getPanel());
resultPanel.add(ngArea.getPanel());
clearAction = new ClearAction();
}
void add(String str, boolean b){
if(b){
okArea.append(str);
}else{
ngArea.append(str);
}
}
JPanel getPanel(){
return resultPanel;
}
ActionListener getClearAction(){
return clearAction;
}
class ClearAction implements ActionListener {
public void actionPerformed(ActionEvent event){
okArea.clear();
ngArea.clear();
}
}
}
class ResultArea {
JPanel panel;
JTextArea textArea;
JScrollPane scrollPane;
JLabel label,counter;
int count;
public ResultArea(String aLabel){
count=0;
panel = new JPanel();
panel.setLayout(new BorderLayout());
textArea = new JTextArea(10,20);
scrollPane = new JScrollPane(textArea);
label = new JLabel(aLabel,SwingConstants.CENTER);
counter = new JLabel("0");
counter.setHorizontalAlignment(SwingConstants.CENTER);
panel.add(scrollPane,BorderLayout.CENTER);
panel.add(label,BorderLayout.NORTH);
panel.add(counter,BorderLayout.SOUTH);
}
JPanel getPanel(){
return panel;
}
void append(String str){
textArea.append(str+"\n");
this.addCounter();
}
private void addCounter(){
counter.setText(Integer.toString(++count));
}
void clear(){
textArea.setText("");
counter.setText("0");
count=0;
}
}
class ControlPanel {
JPanel middlePanel,sliderPanel;
RegField regField;
Executer executer;
ControlPanel(){
regField = new RegField();
executer = new Executer();
middlePanel=new JPanel();
sliderPanel = new JPanel();
middlePanel.add(regField.getTextField());
middlePanel.add(regField.getLabel());
middlePanel.add(executer.getStartButton());
middlePanel.add(executer.getClearButton());
sliderPanel.add(new JLabel("長さ:"));
sliderPanel.add(regField.getSlider());
sliderPanel.add(new JLabel("回数:"));
sliderPanel.add(executer.getNumberSlider());
}
int getTimes(){
return executer.getTimes();
}
void setStartButtonAction(ActionListener al){
executer.setStartButtonAction(al);
}
void setClearButtonAction(ActionListener al){
executer.setClearButtonAction(al);
}
int getLength(){
return regField.getLength();
}
String getText(){
return regField.getText();
}
JPanel getMiddlePanel(){
return middlePanel;
}
JPanel getSliderPanel(){
return sliderPanel;
}
void setErrorMessage(String str){
regField.setErrorMessage(str);
}
}
class RegField {
JTextField inputArea;
JSlider lengthSlider;
JLabel errorMessage;
RegField(){
inputArea = new JTextField("正規表現を入れてください",20);
errorMessage = new JLabel();
lengthSlider = new JSlider(1,20,5);
lengthSlider.setMajorTickSpacing(5);
lengthSlider.setMinorTickSpacing(1);
lengthSlider.setPaintTicks(true);
lengthSlider.setSnapToTicks(true);
lengthSlider.setPaintLabels(true);
}
int getLength(){
return lengthSlider.getValue();
}
JLabel getLabel(){
return errorMessage;
}
void setErrorMessage(String str){
errorMessage.setText(str);
}
JSlider getSlider(){
return lengthSlider;
}
JTextField getTextField(){
return inputArea;
}
String getText(){
return inputArea.getText();
}
}
class Executer {
JButton startButton,clearButton;
JSlider numberSlider;
Executer(){
startButton = new JButton("Start");
clearButton = new JButton("Clear");
numberSlider = new JSlider(1,5,2);
java.util.Hashtable table = new java.util.Hashtable();
table.put(new Integer(1),new JLabel(Integer.toString(pow10(1))));
table.put(new Integer(5),new JLabel(Integer.toString(pow10(5))));
numberSlider.setLabelTable(table);
numberSlider.setMinorTickSpacing(1);
numberSlider.setPaintTicks(true);
numberSlider.setPaintLabels(true);
numberSlider.setSnapToTicks(true);
}
JSlider getNumberSlider(){
return numberSlider;
}
int getTimes(){
return pow10(numberSlider.getValue());
}
JButton getStartButton(){
return startButton;
}
JButton getClearButton(){
return clearButton;
}
void setStartButtonAction(ActionListener al){
startButton.addActionListener(al);
}
void setClearButtonAction(ActionListener al){
clearButton.addActionListener(al);
}
private int pow10(int j){
int ret=1;
for(int i=0; i<j ; i++){
ret *=10;
}
return ret;
}
}
class StringGenerator {
java.util.Random random;
ControlPanel controlPanel;
ResultPanel resultPanel;
ActionListener startButtonAction;
final char[] letter = {'a','b','c','d','e','f','g','h','i','j','k','l',
'm','n','o','p','q','r','s','t','u','v','w','x',
'y','z','A','B','C','D','E','F','G','H','I','J',
'K','L','M','N','O','P','Q','R','S','T','U','V',
'W','X','Y','Z','0','1','2','3','4','5','6','7',
'8','9',' ','\t','1','\"','#','$','%','&','\'',
'(',')','-','=','^','~','\\','|','`','@','{','[',
']','}',';','+',':','*',',','<','.','>','/','?',
'_'};
StringGenerator(ControlPanel cp,ResultPanel rp){
controlPanel = cp;
resultPanel = rp;
random = new java.util.Random();
startButtonAction = this.new StartButtonAction();
}
ActionListener getStartAction(){
return startButtonAction;
}
private char getChar(){
return letter[random.nextInt(letter.length)];
}
String getString(int len){
StringBuffer retStr=new StringBuffer(len);
while(int i=0; i<len; i++){
retStr.append(this.getChar());
}
return retStr.toString();
}
class StartButtonAction implements ActionListener {
public void actionPerformed(ActionEvent event){
int times = controlPanel.getTimes();
String regex = controlPanel.getText();
controlPanel.setErrorMessage("");
try{
for(int i=0; i<times; i++){
String randomString=getString(controlPanel.getLength());
resultPanel.add(randomString,randomString.matches(regex));
}
}catch(Exception e){
controlPanel.setErrorMessage("正規表現に誤りがあります");
}
}
}
}