このドキュメントは http://edu.net.c.dendai.ac.jp/ 上で公開されています。
この授業では、データの解釈方法などを通じて まず、形式言語理論などのプログラミング理論を学びます。 これを習得すると、様々なファイルの形式をプログラムで読めるようになりま す。 キーワードはオートマトン、文脈自由文法、コンパイラコンパイラ、パーサな どです。 そして、 XML に焦点をあて、解釈の原理、ツールの利用法などを学び、 XML 技術を習得します。 キーワードは XML, DOM, SAX などです。 最後に形式言語理論で学んだことを応用し、 GUI におけるイベントの制御を 行います。 キーワードはイベントリスナ、 UML などです。
データ構造とアルゴリズム I では様々なプログラムの定石を学びました。 本講義では、自分で作成したプログラムのルールを実際にプログラムにするよ うな定石を学びます。
この講義を習得すると、さまざまなアプリケーションプログラムを作れるよう になります。 また、大学院受験においてソフトウェアの試験で主に出題されるのがこの講義 の範囲になります。 但し、もっとも基本的なことしか取り上げませんので、 他大学院の受験などでは適宜補う必要があります。
はじめに、入力データの意味や内容を解析するために、文法理論を学びます。 参考書を選ぶ時は、「コンパイラ論」のようなものが参考になります。
コンピュータで記号列を解釈するには、その記号列がどのようなルールで作ら れているかを知る必要があります。 記号列を生成するルールを文法と言います。 そして、生成される記号列すべての集合を言語と言います。 特定の文法を記号 G で表す時、その文法で生成される言語を L(G) と書くこ とがあります。 なお、記号の集合をアルファベット と言います。 しばしばアルファベットはΣで表されます。 そして、そのアルファベットΣから作られる記号列全体の集合を で表します。 なお長さが0の記号列(空列)をεで表します。
正規表現を次のように定義します。
正規表現は一つの文法になりうるので、正規表現で一つの言語を定義すること ができます。
メモリーが有限個しか使えないコンピュータが計算できる言語は正規表現だけ であることが知られています。 このように正規表現はプログラムと非常に密接な関係にありますので、さまざ まなところで使用されています。 例えば、 MS-DOS プロンプトやコマンドプロンプトなどでも、ファイル名を指示 する時、不完全ながらも正規表現が使えます。
但し、これは ab, abab, ababab などの文字列の繰り返しの表現を指定できな いので、完全な意味での正規表現ではありません。 しかし、一応複数のファイル名などを一つの文法で表すことが できます。
コマンドプロンプトまたは MS-DOS プロンプトで c:\windows または
c:\winnt ディレクトリの中のファイルのうち、次の条件に当てはまるファイ
ル名を dir コマンドを使って表示しなさい。
但し、 Windows や MS-DOS はファイル名を二つの文字列を.(ピリオド)でつな
がった形で表しています。
後ろの文字列を拡張子と言います。
例えば「拡張子が txt のファイル名」は
Java ではバージョン 1.4 から java.util.regex パッケージが追加され、次 のような構文で正規表現が使えるようになりました。
java.util.regex.Pattern p = java.util.regex.Pattern.compile("a*b");
java.util.regex.Matcher m = p.matcher("aaaaab");
boolean b = m.matches();
また簡易版として、 java.lang.String クラスに 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){
final String sample="This is a pen.";
final Pattern p = Pattern.compile("\p{Alpha}\p{Alnum}*");
final Matcher m = p.matcher(sample);
while(m.find()){
System.out.println(m.group());
}
}
}
String input="-10+30-20";
char inputstring[]="-10+30-20";
ここまでは正規表現を解釈するツールを利用してきました。 これからは、正規表現をプログラムに変換する手法を学びます。 単純な正規表現ならわざわざツールに頼る必要はありません。
始めにオートマトンについて学びます。 オートマトンとは次のような有向グラフです。
文字列に対して、開始ノードから一文字ずつたどって終了ノードにたどり着け た時、オートマトンはその文字列を受理すると言います。 一方、辺がなかったり、文字列が終った時に終了ノードではなかった時 拒否すると言います。 オートマトンにより、受理する文字列の集合が決定できます。受理する文字列 の集合をそのオートマトンが受理する言語と言います。
各ノードから出る辺に割り当てられている文字がすべて異なるオートマトンを 決定性オートマトンと言います。 このオートマトンは状態遷移図とも呼びます。 決定性オートマトンは入力により流れが分岐するだけですので、簡単にプログ ラムに変換できます。
一方、各ノードから出る辺に割り当てられている文字にダブりがあったり、 ε(空文字)が割り当てられているオートマトンを非決定性オート マトン と言います。 ここでは非決定性オートマトンの受理する言語と正規文法で定められる言語が 一致することを示します。 具体的には正規文法から非決定 性オートマトンへの変換の仕方を示します。 (非決定性オートマトンから正規文法への変換方法は省略します)
帰納法により証明します。
正規表現 a は次のような非決定性オートマトンになります。
正規表現 ab は次のような非決定性オートマトンになります。
但し、このオートマトンでεを考える必要は無いので、省略できます。 このように状態数の少ない等価なオートマトンを導くことを 簡単化と言います。
正規表現 (ab|ac) は次のような非決定性オートマトンになります。
但し、これも最初の入力はどちらも a なので εを考える必要はありません。 また、次の入力 b, c でともに受理状態に入ればいいので、こちらも省略できます。 そのため次のように簡単化できます。
正規表現 (ab|ac)* は次のような非決定性オートマトンになります。
これもこのように簡単化できます。
次の正規表現を受理する非決定性オートマトンを作りなさい。
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 {
private final JPanel resultPanel;
private final ResultArea okArea, ngArea;
private final ClearAction clearAction;
public 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 {
private final JPanel panel;
private final JTextArea textArea;
private final JScrollPane scrollPane;
private final JLabel label,counter;
private 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);
}
public JPanel getPanel(){
return panel;
}
public void append(String str){
textArea.append(str+"\n");
this.addCounter();
}
private void addCounter(){
counter.setText(Integer.toString(++count));
}
private void clear(){
textArea.setText("");
counter.setText("0");
count=0;
}
}
class ControlPanel {
private final JPanel middlePanel,sliderPanel;
private final RegField regField;
private final Executer executer;
public 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());
}
public int getTimes(){
return executer.getTimes();
}
public void setStartButtonAction(ActionListener al){
executer.setStartButtonAction(al);
}
public void setClearButtonAction(ActionListener al){
executer.setClearButtonAction(al);
}
public int getLength(){
return regField.getLength();
}
public String getText(){
return regField.getText();
}
public JPanel getMiddlePanel(){
return middlePanel;
}
public JPanel getSliderPanel(){
return sliderPanel;
}
public void setErrorMessage(String str){
regField.setErrorMessage(str);
}
}
class RegField {
private final JTextField inputArea;
private final JSlider lengthSlider;
private final JLabel errorMessage;
public 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);
}
public int getLength(){
return lengthSlider.getValue();
}
public JLabel getLabel(){
return errorMessage;
}
public void setErrorMessage(String str){
errorMessage.setText(str);
}
public JSlider getSlider(){
return lengthSlider;
}
public JTextField getTextField(){
return inputArea;
}
public String getText(){
return inputArea.getText();
}
}
class Executer {
private final JButton startButton,clearButton;
private final JSlider numberSlider;
public Executer(){
startButton = new JButton("Start");
clearButton = new JButton("Clear");
numberSlider = new JSlider(1,5,2);
final Hashtable table = new 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);
}
public JSlider getNumberSlider(){
return numberSlider;
}
public int getTimes(){
return pow10(numberSlider.getValue());
}
public JButton getStartButton(){
return startButton;
}
public JButton getClearButton(){
return clearButton;
}
public void setStartButtonAction(ActionListener al){
startButton.addActionListener(al);
}
public 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 {
private final Random random;
private final ControlPanel controlPanel;
private final ResultPanel resultPanel;
private final ActionListener startButtonAction;
private 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','\"','#','$','%','&','\'',
'(',')','-','=','^','~','\\','|','`','@','{','[',
']','}',';','+',':','*',',','<','.','>','/','?',
'_'};
public StringGenerator(ControlPanel cp,ResultPanel rp){
controlPanel = cp;
resultPanel = rp;
random = new Random();
startButtonAction = this.new StartButtonAction();
}
public ActionListener getStartAction(){
return startButtonAction;
}
private char getChar(){
return letter[random.nextInt(letter.length)];
}
public String getString(int len){
final 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){
final int times = controlPanel.getTimes();
final String regex = controlPanel.getText();
controlPanel.setErrorMessage("");
try{
for(int i=0; i<times; i++){
final String randomString=getString(controlPanel.getLength());
resultPanel.add(randomString,randomString.matches(regex));
}
}catch(Exception e){
controlPanel.setErrorMessage("正規表現に誤りがあります");
}
}
}
}