[小西ホームページ]   [目次・索引]   [前の授業]   [次の授業]

コンピュータIIB(Javaプログラミング入門)第11回

目次 索引
11.1 文字列
11.1.1 文字列の操作
11.1.2 文字列バッファ
11.1.3 整数と文字列
11.2 文字と文字列
11.2.1 文字の操作
11.2.2 データ圧縮
11.2.3 暗号
11.2.4 探索
11.3 演習11
11.4 レポート課題
暗号化  暗号文  解読    シーザー暗号  線形探索  探索  平文  復号  復号  符号化  文字列バッファ  ランレングス符号 

11.1 文字列

11.1.1 文字列の操作

これまで文字列は、

int result = 12345679 * 9;
System.out.println("result: " + result);

のように画面出力のときのみ使ってきました。 実は、文字列は組み込みのクラスである String クラスのインスタンスです。 文字列はよく使われますので、便利な機能がいくつか用意されています。

まず、コンストラクタを使わなくてもインスタンスが生成できます。 コンストラクタを用いて

String s = new String();
s = "Good";

String s = new String("Good");

などと書かなくても、

String s = "Good";

でよいのです。

また、演算子 + が使えます。 これは、文字列を連接します。

String s1 = "Good";
String s2 = "morning!";
String s3 = s1 + " " + s2;

としますと、 s3 には文字列 "Good morning!" が格納されます。

文字列と数を演算子 + で結びますと、数が文字列に変換されてから連接されます。

int result = 12345679 * 9;
String s1 = "result = ";
String s2 = s1 + result;

としますと、 s2 には文字列 "result = 111111111" が格納されます。

String クラスのメソッドで重要なものは以下の通りです。

char charAt (int i)
そのインスタンスの i 番目の文字を返す。
boolean equals (String s)
そのインスタンスと文字列 s の内容が同じならば true を返す。 そうでなければ false を返す。
int length ()
そのインスタンスの文字数を返す。
static String valueOf (boolean b)
真理値 b を文字列に変換して返す。
static String valueOf (char c)
文字 c を文字列に変換して返す。
static String valueOf (int i)
整数 i を文字列に変換して返す。

なお、メソッドを呼び出すときには、 charint などは書きません。 また、 static と書いてあればクラスメソッド、書いてなければインスタンスメソッドです。 インスタンスメソッド method を呼び出すには、今まで通り

instance.method(argument...)

と書きます。 他のクラス class のクラスメソッド method を呼び出すには、

class.method(argument...)

と書きます。

11.1.2 文字列バッファ

文字列、すなわち String クラスのインスタンスは、一度生成されますとその内容は変更できません。 文字列 "Good morning!" の mor を eve に置き換えることができないのです。 内容を変更するには、文字列ではなく文字列バッファというものを用います。 文字列バッファstring buffer ) とは、組み込みのクラス StringBuffer のインスタンスのことです。 文字列バッファに対しては、文字の置き換え、追加、挿入などができます。 文字列を加工するには、いったん文字列バッファに変換し、処理のあと再び文字列に変換すればよいのです。

文字列を文字列バッファに変換するには、 StringBuffer クラスのコンストラクタを用います。 逆に、文字列バッファを文字列に変換するには、インスタンスメソッド toString() を呼び出します。

String s1 = "Good morning!";
StringBuffer s2 = new StringBuffer(s1);
s2.setCharAt(5, 'e');
s2.setCharAt(6, 'v');
s2.setCharAt(7, 'e');
String s3 = s2.toString();

としますと、 s3 には文字列 "Good evening!" が格納されます。 ここで、メソッド

void setCharAt (int i, char c)

は、そのインスタンスの i 番目の文字を c に置き換えるものです。

StringBuffer クラスの重要なメソッドは以下の通りです。

StringBuffer append (String s)
そのインスタンスに文字列 s を追加する。
StringBuffer append (char c)
そのインスタンスに文字 c を追加する。
char charAt (int i)
そのインスタンスの i 番目の文字を返す。
StringBuffer insert (int i, String s)
そのインスタンスの i 番目の位置に文字列 s を挿入する。
StringBuffer insert (int i, char c)
そのインスタンスの i 番目の位置に文字 c を挿入する。
int length ()
そのインスタンスの文字数を返す。
void setCharAt (int i, char c)
そのインスタンスの i 番目の文字を文字 c に置き換える。
String toString ()
そのインスタンスを文字列に変換して返す。

11.1.3 整数と文字列

Integer クラスでは、整数( int 型)の変換に関するメソッドがいくつか提供されています。 その中では、以下のものが重要です。

static int parseInt (String s)
文字列 s を整数( int 型)に変換して返す。
static String toString (int i)
整数( int 型)を文字列に変換して返す。

ここで、入力された整数をそのまま出力するプログラム

/*  1*/ import java.io.*;
/*  2*/
/*  3*/ class InputTest {
/*  4*/     public static void main (String[] args) throws IOException {
/*  5*/         InputStreamReader isr = new InputStreamReader(System.in);
/*  6*/         BufferedReader br = new BufferedReader(isr);
/*  7*/         int x;
/*  8*/         System.out.print("Enter a number: ");
/*  9*/         x = Integer.parseInt(br.readLine());
/* 10*/         System.out.println(x);
/* 11*/     }
/* 12*/ }
b04a001@AsiaA1:~/java% javac InputTest.java
b04a001@AsiaA1:~/java% java InputTest
Enter a number: 100
100
b04a001@AsiaA1:~/java%

を思い出してください。 このプログラムの9行目の意味が、ここでやっと明らかになります。

このプログラムを実行しますと、式 br.readLine() の値は入力された文字列になります。 上記の場合では、文字列 "100" です。 そして、クラスメソッド parseInt によって文字列 "100" が整数 100 に変換されます。 最後に、この整数 100 が変数 x に格納されるのです。


11.2 文字と文字列

11.2.1 文字の操作

前回説明した通り、文字は内部では整数として処理されます。 したがって、文字が等しいかどうかは、関係演算子 == を用いて表します。 また、 >< などは、文字符号の大小関係を意味します。

例えば、

if (c == 'Z') {
    ...
}

c が文字 Z ならば…ですし、

if (c > 'Z') {
    ...
}

c の文字符号が Z の文字符号より大きいならば…です。

文字を1増加させることは、文字符号を1増加させることになります。 ただし、このことを

c = c + 1;

と書きますと、エラーになります。 これは、足し算によって右辺が整数( int 型)と見なされるからです。 整数を文字に変換するには、

c = (char) (c + 1);

と書きます。 逆に、整数と見なされることを利用することもできます。 文字 c が数字である場合、式

c - '0'

はその数字の表す数になります。

文字に関するメソッドは、組み込みのクラスである Character クラスで提供されています。 この中で重要なものは以下の通りです。

static boolean isDigit (char c)
文字 c が数字ならば true を返す。 そうでなければ false を返す。
static boolean isLowerCase (char c)
文字 c がアルファベットの小文字ならば true を返す。 そうでなければ false を返す。
static boolean isSpace (char c)
文字 c がホワイトスペース(スペース、水平タブ、改行など)ならば true を返す。 そうでなければ false を返す。
static boolean isUpperCase (char c)
文字 c がアルファベットの大文字ならば true を返す。 そうでなければ false を返す。
static char toLowerCase (char c)
文字 c がアルファベットの大文字ならば、それを小文字に変換して返す。 そうでなければそのまま返す。
static char toUpperCase (char c)
文字 c がアルファベットの小文字ならば、それを大文字に変換して返す。 そうでなければそのまま返す。

11.2.2 データ圧縮

文字列の応用例として、 ランレングス符号run-length coding ) というデータ圧縮法を取り上げます。 ここでは、ランレングス符号を復号して、もとのイメージを画面に出力します。 なお、 復号decode ) するとは 符号化encode ) されたデータを元に戻すことです。 ランレングス符号は、ファックス伝送の基本にもなっています。 ファックス伝送の仕組みを簡単に紹介し、その中でランレングス符号について説明します。

ファックス伝送は、はじめに送信側が紙面イメージを読み込みます。 このとき、紙面イメージを75分の1インチ程度の単位で格子状に分割します。 そして、光学的な方法でそれぞれの升を白か黒かに決定します。 升を横にたどっていき、右端に達したら左端に戻るということを、紙面がつきるまで繰り返し、白黒の列を作ります。 最後に、この白黒データを受信側に送信します。

White-black data and paper image
図 11.1  白黒データと紙面イメージ

受信側では、まずこの白黒データを受け取ります。 そして、白黒データと一緒に紙面の升をたどっていき、データが黒ならばその部分を黒く記録します。 これを繰り返しますと、もとの紙面イメージができあがるのです。

ここで、白と黒をそれぞれ WB で表わすことにしまして、送信側が白黒データ

WWWWWBBBBBBBWWWWWWWWWWWWWWWWWWWWWWWWWWWBBBBBWWWWWW

を構成したとします。 白黒データが長ければ長いほど、伝送に時間がかかりますので、なるべく短くしたいです。 そこで、5個の W 、7個の B 、27個の W 、…、すなわち

5W7B27W5B6W

という符号化データを送ることにします。 これがランレングス符号です。 受信側では、 5WWWWWW に置き換え、 7BBBBBBBB に置き換え、…という復号操作を行ないますと、データを元に戻すことができます。

ランレングス符号は、同じ文字が何度も繰り返されるデータほど効果を発揮します。 なお、実際に圧縮する場合には、数の表現に工夫が必要です。

今、受信側がランレングス符号

16WE7W2B7WE7W2B7WE6W1B1W2B6WE6W1
B1W2B6WE5W1B3W2B5WE5W1B3W2B5WE4W
1B5W2B4WE4W1B5W2B4WE3W1B7W2B3WE3
W10B3WE2W1B9W2B2WE2W1B9W2B2WE1W1
B11W2B1WE1W1B11W2B1WE3B9W4BE

を受け取ったとします。 これを復号して、元の紙面イメージを画面に出力することが、このプログラムの目的です。 画面では、例えば白を . で、黒を * で表します。 なお、文字 E は紙の右端を表すものとします。 画面では改行します。

アルゴリズムは次のようになります。

  1. ループ制御変数 i を宣言する。
  2. 文字を格納する変数 c を宣言する。
  3. 白黒の繰返し回数を表す変数 times を宣言し、初期値を0とする。
  4. String 型の変数 code を宣言し、受信した符号で初期化する。
  5. 変数 i の値を0から符号の長さ未満の間増加させながら、以下を繰り返す。{
  6.     符号の i 番目の文字を変数 c に格納する。
  7.     もし c が数字ならば、{
  8.         変数 times の値を10倍して c の表す数を加える。
  9.     }そうでなくて、 c が'W'ならば、{
  10.         '.'を times 回、改行なしで出力する。
  11.         式0を変数 times に格納する。
  12.     }そうでなくて、 c が'B'ならば、{
  13.         '*'を times 回、改行なしで出力する。
  14.         式0を変数 times に格納する。
  15.     }そうでなくて、 c が'E'ならば、{
  16.         改行する。
  17.         式0を変数 times に格納する。
  18.     }どれでもないならば、{
  19.         (何もしない)
  20.     }

プログラムは以下のようになります。

/*  1*/ class RunLength {
/*  2*/     public static void main (String[] args) {
/*  3*/         int i;
/*  4*/         char c;
/*  5*/         int times = 0;
/*  6*/         String code = "16WE7W2B7WE7W2B7WE6W1B1W2B6WE6W1"
/*  7*/                       + "B1W2B6WE5W1B3W2B5WE5W1B3W2B5WE4W"
/*  8*/                       + "1B5W2B4WE4W1B5W2B4WE3W1B7W2B3WE3"
/*  9*/                       + "W10B3WE2W1B9W2B2WE2W1B9W2B2WE1W1"
/* 10*/                       + "B11W2B1WE1W1B11W2B1WE3B9W4BE";
/* 11*/         for (i = 0; i < code.length(); i++) {
/* 12*/             c = code.charAt(i);
/* 13*/             if (Character.isDigit(c)) {
/* 14*/                 times = times * 10 + c - '0';
/* 15*/             } else if (c == 'W') {
/* 16*/                 printChars('.', times);
/* 17*/                 times = 0;
/* 18*/             } else if (c == 'B') {
/* 19*/                 printChars('*', times);
/* 20*/                 times = 0;
/* 21*/             } else if (c == 'E') {
/* 22*/                 System.out.println();
/* 23*/                 times = 0;
/* 24*/             } else {
/* 25*/             }
/* 26*/         }
/* 27*/     }
/* 28*/     static void printChars (char c, int times) {
/* 29*/         int i;
/* 30*/         for (i = 0; i < times; i++) {
/* 31*/             System.out.print(c);
/* 32*/         }
/* 33*/     }
/* 34*/ }
b04a001@AsiaA1:~/java% java RunLength
................
.......**.......
.......**.......
......*.**......
......*.**......
.....*...**.....
.....*...**.....
....*.....**....
....*.....**....
...*.......**...
...**********...
..*.........**..
..*.........**..
.*...........**.
.*...........**.
***.........****
b04a001@AsiaA1:~/java%

11.2.3 暗号

もう一つの応用例として、シーザー暗号を取り上げます。 シーザー暗号Caesar cipher ) とはシーザー(カエサル)が用いたとされる暗号化の方法です。 また、 暗号化encryption ) とは、秘密にしたいメッセージ( 平文plaintext ) を特定の方法で第三者には読めない形式( 暗号文ciphertext ) にすることです。

暗号を用いた通信は、一般的には次の通りです。 まず、メッセージの送信者は、暗号化の方法の他に、 key ) とよばれるデータを決定します。 そして、メッセージと鍵から暗号文を構成し、受信者に送信します。 受信者は、暗号化の方法とその鍵を知っています。 暗号文を受けとりますと、鍵を使って暗号文を元のメッセージに戻します( 復号decryption )。 第三者は、暗号化の方法は知っていても鍵を知らないので、元のメッセージに戻すことが困難になります。 なお、鍵を使わずに元のメッセージに戻すことを 解読cryptanalysis ) と言います。

Encryption and decryption
図 11.2  暗号化と復号

ここでは簡単のため、平文はアルファベット大文字の並びとします。 また、スペースや各種記号は解読のヒントになりますので取り除いておきます。

シーザー暗号では、鍵 K は整数です。 平文のアルファベットを一文字ずつ K だけずらしたアルファベットに置き換えて、暗号化を行ないます。 例えば K = 3 でしたら、平文

MYNAMEISKYOKOAZUMA(私の名前は東京子です。)

は暗号文

PBQDPHLVNBRNRDCXPD

に暗号化されます。 アルファベットの置き換えは以下の表の通りです。 YB に置き換えられることに注意してください。

表 11.1  シーザー暗号でのアルファベットの置き換え(鍵 K = 3 の場合)
平文 A B C D E F G H I J K L M
暗号文 D E F G H I J K L M N O P
平文 N O P Q R S T U V W X Y Z
暗号文 Q R S T U V W X Y Z A B C

この表を逆にたどりますと復号ができます。 K = 26 - 3 = 23 としてもう一度暗号化することでも復号可能です。

なお、シーザー暗号はすぐに解読されます。 なぜならば、 K = 1 の場合、 K = 2 の場合、…、 K = 25 の場合をすべて確かめ、その中から意味の通るものを選べばよいからです。 ここでシーザー暗号を取り上げたのは、暗号の仕組みに慣れるためです。

アルゴリズムは次のようになります。

  1. ループ制御変数 i を宣言する。
  2. 文字を格納する変数 c を宣言する。
  3. 鍵を格納する変数 key を宣言する。
  4. String 型の変数 plain を宣言する。
  5. 文字列"Enter a key: "を改行なしで出力する。
  6. 入力された数を変数 key に格納する。
  7. 文字列"Enter a plaintext: "を改行なしで出力する。
  8. 入力された文字列を変数 plain に格納する。
  9. 変数 i の値を0から平文の長さ未満の間増加させながら、以下を繰り返す。{
  10.     平文の i 番目の文字を変数 c に格納する。
  11.     もし c がアルファベットの大文字ならば、{
  12.         変数 c の値を key ずらす。
  13.         もし c が'Z'以降になったならば、{
  14.             変数 c の値を 26 戻す。
  15.         }
  16.     }
  17.     式 c の値を改行なしで出力する。
  18. 改行する。

プログラムは以下のようになります。

/*  1*/ import java.io.*;
/*  2*/
/*  3*/ class CaesarCipher {
/*  4*/     public static void main (String[] args) throws IOException {
/*  5*/         InputStreamReader isr = new InputStreamReader(System.in);
/*  6*/         BufferedReader br = new BufferedReader(isr);
/*  7*/         int i;
/*  8*/         char c;
/*  9*/         int key;
/* 10*/         String plain;
/* 11*/         System.out.print("Enter a key: ");
/* 12*/         key = Integer.parseInt(br.readLine());
/* 13*/         System.out.print("Enter a plaintext");
/* 14*/         plain = br.readLine();
/* 15*/         for (i = 0; i < plain.length(); i++) {
/* 16*/             c = plain.charAt(i);
/* 17*/             if (Character.isUpperCase(c)) {
/* 18*/                 c = (char) (c + key);
/* 19*/                 if (c > 'Z') {
/* 20*/                     c = (char) (c - 26);
/* 21*/                 }
/* 22*/             }
/* 23*/             System.out.print(c);
/* 24*/         }
/* 25*/         System.out.println();
/* 26*/     }
/* 27*/ }
b04a001@AsiaA1:~% java CaesarCipher
Enter a key: 3
Enter a plaintext: MYNAMEISKYOKOAZUMA
PBQDPHLVNBRNRDCXPD
b04a001@AsiaA1:~% java CaesarCipher
Enter a key: 23
Enter a plaintext: PBQDPHLVNBRNRDCXPD
MYNAMEISKYOKOAZUMA
b04a001@AsiaA1:~%

11.2.4 探索

最後の例は探索です。 探索search )とは、データのかたまりの中から、要求されたデータの位置を特定し、その位置のデータを取り出すことです。 ここでは、文字列

MYNAMEISKYOKOAZUMA

から文字を探索し、その位置を求めます。 入力された文字を、文字列の0番目の文字、1番目の文字、…と順番に比較し、一致したらそこで終了します。 最後まで一致しなかったら、文字がないと答えます。

なお、このような、最初から順番に調べる探索法は、 線形探索linear search )とよばれます。 線形探索では、データが存在しないと結論づけるためには、すべてのデータを調べなくてはなりません。 また、同じデータが複数ある場合、最初のデータが取り出されます。

アルゴリズムは次のようになります。

  1. String 型の変数 str を宣言し、初期値を"MYNAMEISKYOKOAZUMA"とする。
  2. 調べる位置を表す変数 pos を宣言し、初期値を 0 とする。
  3. char 型の変数 c を宣言する。
  4. 文字列"Enter a character: "を改行なしで出力する。
  5. 入力された数を変数 c に格納する。
  6. pos が文字列 str の長さ未満の間、以下を繰り返す。{
  7.     もし文字列 strpos 番目の文字が c と等しいならば、{
  8.         式 pos の値を出力する。
  9.          return 文で終了する。
  10.     }そうでなければ、{
  11.         変数 pos の値を 1 増やす。
  12.     }
  13. 文字列"Not found."を出力する。

プログラムは以下のようになります。

/*  1*/ import java.io.*;
/*  2*/
/*  3*/ class LinearSearch {
/*  4*/     public static void main (String[] args) throws IOException {
/*  5*/         InputStreamReader isr = new InputStreamReader(System.in);
/*  6*/         BufferedReader br = new BufferedReader(isr);
/*  7*/         String str = "MYNAMEISKYOKOAZUMA";
/*  8*/         int pos = 0;
/*  9*/         char c;
/* 10*/         System.out.print("Enter a character: ");
/* 11*/         c = (char) br.read();
/* 12*/         while (pos < str.length()) {
/* 13*/             if (str.charAt(pos) == c) {
/* 14*/                 System.out.println(pos);
/* 15*/                 return;
/* 16*/             } else {
/* 17*/                 pos++;
/* 18*/             }
/* 19*/         }
/* 20*/         System.out.println("Not found.");
/* 21*/     }
/* 22*/ }
b04a001@AsiaA1:~/java% java LinearSearch
Enter a character: N
2
b04a001@AsiaA1:~/java% java LinearSearch
Enter a character: A
3
b04a001@AsiaA1:~/java% java LinearSearch
Enter a character: P
Not found.
b04a001@AsiaA1:~/java%

11.3 演習11

入力された文字列をランレングス符号で符号化してください。 入力文字列に数字は含まれないものとします。

アルゴリズムは次のようにします。

  1. ループ制御変数 i を宣言する。
  2. 文字を表す変数 c を宣言する。
  3. 文字数を表す変数 times を宣言する。
  4. String 型の変数 s を宣言する。
  5. 文字列"Enter a string: "を改行なしで出力する。
  6. 入力された文字列を変数 s に格納する。
  7. もし文字列の長さが0ならば、{
  8.     文字列"Empty string."を出力する。
  9. }そうでないならば、{
  10.     文字列の0番目の文字を変数 c に格納する。
  11.     1を変数 times に格納する。
  12.     変数 i の値を1から文字列の長さ未満の間増加させながら、以下を繰り返す。{
  13.         もし文字列の i 番目の文字が c と異なるならば、{
  14.             式 times の値を改行なしで出力する。
  15.             式 c の値を改行なしで出力する。
  16.             文字列の i 番目の文字を変数 c に格納する。
  17.             1を変数 times に格納する。
  18.         }そうでないならば、{
  19.             変数 times の値を1増やす。
  20.         }
  21.     }
  22.     式 times の値を改行なしで出力する。
  23.     式 c の値を出力する。

このアルゴリズムをプログラムにしてください。

b04a001@AsiaA1:~/java% java Compress
Enter a string: WWWWWBBBBBBBWWWWWWWWWWWWWWWWWWWWWWWWWWWBBBBBWWWWWW
5W7B27W5B6W
b04a001@AsiaA1:~/java%

(今回は、余力のある人の問題はありません。)


11.4 レポート課題

今日の演習11に従ってJavaプログラムを作成し、そのプログラムをkonishi@twcu.ac.jpあてにメールで提出してください。 メールには、学生番号、氏名、科目名、授業日(7/2)を明記してください。


[小西ホームページ]   [目次・索引]   [前の授業]   [次の授業]

2004年7月2日更新
konishi@twcu.ac.jp
Copyright (C) 2004 Zenjiro Konishi. All rights reserved.