Back To Main

┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
┃ ◆Javaで珈琲ブレイク vol.015 ◆
┃……………………………………………………………………………………………
┃ [不定期] まぐまぐ ID=0000088576 Melma! ID=m00061296
┃……………………………………………………………………………………………
┃ 今回からご覧になる方は、バックナンバーご活用下さい
┃ http://javacafebreak.tripod.com
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

◆目次◆

■Question13の解答
■Question 11のNo.2
■『全て』がアルファベットの大文字か小文字の証明は大変な労力が必要
■checkAccountId() メソッド

皆さん、こんにちは。

Mr.Hackです。

いやー、日本は暑いですねー。この間、2週間ほど、日本に帰国していたのですが、梅雨は梅雨でむしむし、台風直撃、梅雨が明けると、とたんに30度を超える猛暑と、なかなか手強いですね。おかげで、体を日本の気候にあわせるのが精一杯でした。今回は、手見上げに、PCカードを二回り大きくしたぐらいの無線プリンターサーバーを買って帰りました。これは、メルコからでているのですが、思わず一目惚れ、衝動買いをしてしまいました。とーっても、ちっちゃくていいと思いますよ。メルコのプリンターサーバ。LANを組んでいれば、普通の人はたいていプリンターはルータのそばに置くでしょうから、わざわざ無線にする必要はないでしょうね・・・。僕の家では、ルーター兼無線アクセスポイントとプリンターが寝室に、パソコンは普段別の部屋でやるので、プリントするたびにわざわざ寝室にいかなければならず不便だったのです。今度は、プリンターはすぐそばの場所におけそうです。プリンターの配置に困っている方は、無線プリンターサーバーはいい選択肢だと思います。

さて、前回は、ド・モルガンの法則を勉強しました。これを習得することによって、if文のような条件節に強くなれるということをお話ししました。それでは、早速宿題をみてみましょう。

■Question13の解答
┌──────────────────────────────────┐
1.ある文字が、数字(0-9)、または大文字(A-Z)、または小文字(a-z)を
含んでいれば、tureを返す(それ以外はfalseを返す)下記のメソッドを完成
させよ。

boolean checkCharacter(char character) {
if (数字か大文字か小文字) {
return true;
}
else {
return false;
}
}
└──────────────────────────────────┘

ある文字(キャラクタ、character)が数字であることをチェックするには、

┌──────────────────────────────────┐
character >= '0' && character <= '9'
└──────────────────────────────────┘

でした。同じように、characterが大文字であることをチェックするには、

┌──────────────────────────────────┐
character >= 'A' && character <= 'Z'
└──────────────────────────────────┘

で、小文字であることをチェックするには、

┌──────────────────────────────────┐
character >= 'a' && character <= 'z'
└──────────────────────────────────┘

です。characterがこのうちのいずれかであればいいので、||を使って

┌──────────────────────────────────┐
if ((character >= '0' && character <= '9') ||
(character >= 'A' && character <= 'Z') ||
(character >= 'a' && character <= 'z')) {
return true;
}
else {
return false;
}
└──────────────────────────────────┘

となります。

今度は、”『数字か大文字か小文字』ではない”場合は、falseを返すようにした2.をみてみましょう。

┌──────────────────────────────────┐
2.下記のメソッドが1.と同じ意味になるように、if文の条件conditionを
設定せよ。

boolean checkCharacter(char character) {
if (condition) {
return false;
}
else {
return true;
}
}
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

先ほど、1.でelse節にあった条件を、if節に持ってきてあげればいいわけですが、elseの条件は、if節の条件の補集合に当たりますので、

┌──────────────────────────────────┐
else節の条件 = 『if節の条件』でない条件 = NOT 『if節の条件』
└──────────────────────────────────┘

が成り立ちます。つまり、if節の条件を否定してあげればいいですね。

┌──────────────────────────────────┐
NOT 『if文の条件』 = NOT ( ((character >= '0' && character <= '9') ||
(character >= 'A' && character <= 'Z') ||
(character >= 'a' && character <= 'z')) )
└──────────────────────────────────┘

ここで、ド・モルガンの法則を思い出してください。

┌──────────────────────────────────┐
NOT (A OR B) = (NOT A) AND (NOT B)
└──────────────────────────────────┘

でしたね。(A OR B)の全体を否定すると、それぞれの部分を否定し、ORがAND(ANDの場合はORに)なりましたね。このド・モルガンの法則を適用すると、

┌──────────────────────────────────┐
NOT ( (character >= '0' && character <= '9') ||
(character >= 'A' && character <= 'Z') ||
(character >= 'a' && character <= 'z') )
└──────────────────────────────────┘

のそれぞれの部分、(character >= '0' && character <= '9') 、(character >= 'A' && character <= 'Z')、(character >= 'a' && character <= 'z')) を否定し、||を&&に変えてあげればいいですね。(character >= '0' && character <= '9') を否定するということは、補集合をとればいいですから、数字ではないキャラクタ、つまり、(character < '0' || character > '9')になります。実は、ここもド・モルガンの法則を適用しているのがわかりますか?

┌──────────────────────────────────┐
NOT ( (character >= '0' && character <= '9') ) //ド・モルガンの法則
= (NOT (character >= '0')) || (NOT (character <= '9'))
= (character < '0') || (character > '9')
└──────────────────────────────────┘

となります。同じように、(character >= 'A' && character <= 'Z')、(character >= 'a' && character <= 'z')) にド・モルガンの法則を適用しそれぞれ、
┌──────────────────────────────────┐
NOT (character >= 'A' && character <= 'Z')
= (character < 'A') || (character > 'Z')

NOT (character >= 'a' && character <= 'z')
= (character < 'a') || (character > 'z')
└──────────────────────────────────┘

を得ます。故に、

┌──────────────────────────────────┐
NOT ( (character >= '0' && character <= '9') ||
(character >= 'A' && character <= 'Z') ||
(character >= 'a' && character <= 'z') )

= ( (character < '0') || (character > '9') &&
(character < 'A') || (character > 'Z') &&
(character < 'a') || (character > 'z') )

= condition
└──────────────────────────────────┘

となります。どうです?機械的に記号を変えていくだけなので、簡単でしょう?また、機械的にできるので、正確に答えを導き出すことができます。

宿題でウォーミングアップができたところで、Question 11に戻ってみましょう。Question 11はBankアカウントを作るときに、適切なIDとPasswordを要求するように設定する問題でした。もう一度みてみましょう。

■Question 11のNo.2
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

2. アカウントIDの仕様は以下の通り

(a) アカウントIDはアルファベットの大文字か小文字のみ
(b) 最低3文字以上の文字列から構成されなければならない
(例)Mrhack、mRh

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

(a)からしなければならないことは、ある文字列(String)の一つ一つのキャラクタが、アルファベットの大文字か、小文字でなければなりませんね。これは、Question 13で使ったような方法を使います。

(b)は、3文字以上のStringから構成されなければならないので、String#lengh()メソッドを使えば文字数をチェックできますね。

ユーザーが作成しようとしているIDをチェックするメソッド、checkAccountId()をBankAccountクラスに作りましょう。メソッドの定義は、

┌──────────────────────────────────┐
boolean checkAccountId(String accountId)
└──────────────────────────────────┘

として、accountIdが(a)、(b)の条件を満たしていればtrueを、満たしていなければfalseを返すようなメソッドです。このメソッドを作ると何が便利かといいますと、BankAccountのインスタンスを作るとき(初期化時)に、accountIdとして渡されたStringが適切かどうかチェックできますね。適切でなければ、RantimeExcpetionのサブクラスである、IllegalArgumentExceptionをスローできますね(この辺がわからない方は、vol.007を参照)。あるいは、setAccountId()メソッドを作った時も、そのメソッドの中で、checkAccountId()メソッドの戻り値booleanをチェックして、同じように、IllegalArgumentExceptionをスローできますね。

それでは、checkAccountId()メソッドの中はどのようなことをしなければならないでしょうか?

┌──────────────────────────────────┐
boolean checkAccountId(String accountId) {

// accountIdがnullか空文字でないかをチェック。
//null・空文字場合はfalse。

// (b) の3文字以上をチェック。3文字未満の場合はfalse。

// (a) のアルファベットの大文字か小文字をチェック。
// それ以外の文字の場合はfalse。

// 上記を全てクリアーした場合は、trueを返す

}
└──────────────────────────────────┘

ある文字列(String型)が適切な文字列かどうかをチェックするときは、かならず『nullかどうか』をチェックしたいですね。というのも、String型は参照型ですので、オブジェクト(メモリーアドレス)を指していない状態(null)が存在します。そのnullであるString型の参照変数をcheckAccountId()メソッドに引数として渡したときに、なにかString型のメソッド(たとえば、length()メソッド)を適用すると、NullPointerExceptionが発生します。これは、RuntimeExceptionのサブクラスです。つまり、コンパイル時にはエラーをチェックされないため、見落とす可能性がありますね。参照型のメソッドを引数として使うときは、nullの場合も考えましょう。

nullでなければ、次は、空文字であるかどうかを、equals()メソッドやlength()メソッドを使ってチェックできますね。もし、nullや空文字の場合は、accountIdとして適さないので、falseを返します。

┌──────────────────────────────────┐
if (accountId == null || accountId.length() == 0) {
return false;
}
└──────────────────────────────────┘


ところで、もしここで、falseの代わりに、trueを返したいときは、どうするのでしたっけ?そうですド・モルガンの法則ですね。if文の条件を否定して、return falseをreturn trueに変えればできあがりです。

┌──────────────────────────────────┐
if (accountId != null && accountId.length() != 0) {
return true;
}
└──────────────────────────────────┘

です。もう簡単ですね。

さて、この文をクリアーしたaccounIdはnullでも空文字でもない文字列です。

(b)の3文字以上からなる文字列はどうしましょうか?そうです。同じように、length()メソッドをつかって accountId.length() < 3 というようにチェックできますね。

┌──────────────────────────────────┐
if (accountId.length() < 3) {
return false;
}
└──────────────────────────────────┘

ここで、お気づきの方もいるかと思います。空文字が文字列の長さがゼロなので、3文字未満の文字列も同様にfalseを返すようにすればいいので、まとめて、
┌──────────────────────────────────┐
if (accountId == null || accountId.length() < 3) {
return false;
}
└──────────────────────────────────┘

とできますね。accountId == 0 という条件は、accountId.length() < 3に含まれます。

さて、(a)はどうしましょうか?キャラクタのチェックは、一文字一文字づつしかチェックできないので、文字列全体をチェックする場合(たとえば、Mr.Hackという文字列)は、ループを使って文字を一つずつチェックしなければなりません。String型の実態は、char型の配列だということを以前お話ししました。そのため、各配列にアクセスできるString#charAt(int index)メソッドがあります。これを使えば配列の各indexに対応する文字を得ることができますのでfor ループ等でi (index)を一つずつ進めてあげれば良さそうです。

┌──────────────────────────────────┐
for (int i = 0; i < accountId.length(); i++) {
char character = accountId.charAt(i);
//characterをチェック
}
└──────────────────────────────────┘

//でcharacterをチェックしたいわけですが、どうチェックすればいいでしょう?文字が正しい場合にtrueを返す方がいいですか?それとも、正しくない場合にfalseを返すようにした方がいいですか?

ここには、すごく重要な考えが潜んでいます。

■『全て』がアルファベットの大文字か小文字の証明は大変な労力が必要
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

String型であるaccountIdの一つ一つの文字character全てがアルファベットの大文字か小文字で証明するためには、大変な労力がいります。言い換えますと、accountIdが全てアルファベットの大文字か小文字である文字列で、trueを返すようにプログラムを書きますと、大変な労力がかかります。もし、accountIdが1000桁からなる文字列だとすると、この文字列が全てアルファベットの大文字か小文字でなければなりませんから、全てのindexの文字を調べて大文字・小文字か否かを確認しなければなりません。たとえば、for ループを使って

┌──────────────────────────────────┐

boolean isUpperOrLower = true; //フラグをセット
for (int i = 0; i < 1000; i++) {
char character = accountId.charAt(i);
//characterをチェック
if (characterがアルファベットの大文字か小文字) {
}
else {
isUpperOrLower = false;
}
}

if (isUpperOrLower) {
// isUppserOrLowerにfalseがセットされていない。
// = accountIdのキャラクタは全てアルファベットの大文字か小文字
// つまり、accountIdは適切な文字列だと証明された。
return true;
}

└──────────────────────────────────┘

全ての文字のチェックが終わってフラグであるisUpperOrLowerがfalseとセットされなければ、accountIdは適切な文字列だと証明できますね。しかしながら、その証明には、1000桁あれば必ず1000桁分ループしなければなりません。これは、桁が増えれば増えるだけ大変なコストがかかります。

しかしながら、逆に、accountIdが不適切な文字列だと証明するためには、最短でループは1回(例えば、"@Mrhack.....")、多くて1000回(例えば、"....mrhack&")とばらつきがでます。というのも、1000桁あるうちのどれか一つだけでも、アルファベットの大文字か小文字でない文字が含まれていることを証明できれば、その全体のaccountIdは不適切な文字列だと証明できるからです。

つまり、accountIdが適切な文字列であること(すべの文字がアルファベットの大文字か小文字であること)を証明することよりも、accountIdが不適切であることを証明する方が労力がかからないのです。

アルゴリズムのコストの観点から考えれば、for ループの中でチェックしなければならないのは、『アルファベットの大文字か小文字でない』文字ということになりますね。その場合に、return falseを返すようにすれば、全ての文字をチェックしなくても、accountIdが不適切な文字ということを証明できます。コードはこんな感じになります。

┌──────────────────────────────────┐
for (int i = 0; i < accountId.length(); i++) {
character = accountId.charAt(i);
if (アルファベットの大文字か小文字でない場合) {
// ループが length() - 1 までいかなくても、
// falseを返す場合有り。
// つまり、その時、accountIdは全てアルファベットの大文字・小文字
// ではないことが証明された。
return false;
}
}
└──────────────────────────────────┘

アルファベットの大文字か小文字の場合は、

┌──────────────────────────────────┐
(character >= 'A' && character <= 'Z') ||
(character >= 'a' && character <= 'z')
└──────────────────────────────────┘

ですので、それでは『ない』場合は、否定をとってド・モルガンの法則を適用すればいいので、

┌──────────────────────────────────┐
(character < 'a' || character > 'z') &&
(character < 'A' || character > 'Z')
└──────────────────────────────────┘

となります。

以上をふまえて、checkAccountId()メソッドは下記のようになります。

■checkAccountId() メソッド
┌──────────────────────────────────┐
public boolean checkAccountId(String accountId) {
/**
* accountIdがnullか3文字未満の場合
*/
if (accountId == null || accountId.length() < 3) {
return false;
}
char character;
/**
* NOT ( (character >= 'A' && character <= 'Z') ||
* (character >= 'a' && character <= 'z') )
* =
*
* (character < 'a' || character > 'z') &&
* (character < 'A' || character > 'Z')
*/
for (int i = 0; i < accountId.length(); i++) {
character = accountId.charAt(i);
if ((character < 'a' || character > 'z') &&
(character < 'A' || character > 'Z')) {
return false;
}
}
/**
* 全ての事前条件をクリアーすれば、return true
*/
return true;
}
└──────────────────────────────────┘

今回は、ド・モルガンの法則を応用して、アカウントIDがアルファベットの大文字・小文字で、三文字以上であるという条件をcheckAccountId()というメソッドで作成しました。また、”全てが正しい”(文字列がアルファベットの大文字か小文字であること)ということを言う(証明する)ことは、非常に労力がかかることを勉強しました。逆に、一つでも『正しくない』(アルファベット以外の文字が文字列に含まれる)ことがいえれば”全てが正しい”ということが”正しくない”と証明できることも学びました。少し、難しい内容になりましたが、プログラムを書く上で大事なことですので、復習しておいてください。

それでは、また、あいましょう。

Mr.Hack


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◆Javaで珈琲ブレイク◆
───────────────────────────────────
皆様からの激励・批判のメールをお待ちしております。
【発行者】 Mr.Hack - javacafebreak@hotmail.com
【掲示板】 https://javacafebreak.tripod.com/cafe_entrance.html
※質問は上記の掲示板からどうぞ。
【サイト】 http://javacafebreak.tripod.com
【発行数】 まぐまぐ[1003] Melma[158]
【解除】http://javacafebreak.tripod.com
※解除は上記のサイトからいつでもできます。
────────────────────────────────────
(c)2002 MR.HACK ALL RIGHTS RESERVED
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

© 2002 MR.HACK ALL RIGHTS RESERVED