「プロになるJava」ボツ原稿、今回は「13章 処理の難しさの段階」の「隠れた状態を扱う処理」のもうひとつの例として、カッコの対応を判定する処理です。13.1.2のあとに入る想定です。
書籍にのせたランレングス圧縮よりもカッコの対応の判定のほうが「隠れた状態」がわかりやすいのですが、重複を省く処理からの流れでランレングス圧縮を残しました。
隠れた状態を扱う処理
カッコの対応の判定
「((test(data))test)
」のような文字列で、カッコ「(~)」が正しく対応しているかどうかを判定するプログラムを考えます。このとき、単純に前の要素を見ても、正しく対応しているかどうかの判断はできません。
最初に思いつくのは、開きカッコと閉じカッコが同じ数だけあればいいというものです。確かに先にあげた例では開きカッコが3つ、閉じカッコが3つで正しく対応しています。しかし、「)test(
」では開きカッコと閉じカッコの数は正しいものの、正しく対応してるとは言えませんね。
開きカッコの数を数えるという方針は正しいのですが、閉じカッコは開きカッコと別に数えるのではなく、閉じカッコが来ると開きカッコの数を減らしていくようにすれば対応を判定できます。最終的に0になれば全ての開きカッコが正しく閉じられ、途中でマイナスになるなら閉じカッコが多すぎると判断できます。
src/main/java/projava/CheckParentheses.java
package projava; public class CheckParentheses { static boolean check(String data) { int count = 0; for (var ch : data.toCharArray()) { switch (ch) { case '(' -> count++; case ')' -> { count--; if (count < 0) { return false; } } } } return count == 0; } public static void main(String[] args) { System.out.println(check("((test(data))test)" )); System.out.println(check("((test(data)test)" )); System.out.println(check("()test)data(test" )); } }
実行すると、main
メソッドでのカッコの対応が取れているのは最初の行だけなので、次のように表示されます。
true false false
処理はcheck
メソッドとして書いていきます。判定する文字列を引数data
で受け取ります。カッコの対応が正しければtrue
を返すことにします。
static boolean check(String data) {
変数count
で開きカッコの数を管理します。
int count = 0;
文字列中の文字の数だけ繰り返します。
for (var ch : data.toCharArray()) {
取り出した文字で処理を振り分けるようswitch
文で分岐します。
switch (ch) {
開きカッコの場合は変数count
の値を増やします。
case '(' -> count++;
閉じカッコの場合は変数count
の値を減らします。
case ')' -> { count--;
このとき、もし変数count
の値が負になっていれば、対応が取れていないことをあらわすfalse
を返して処理を終了します。
if (count < 0) { return false; }
すべての文字を処理したあと、変数count
が0であれば対応が取れていることをあらわすtrue
が返るようにして処理を終了します。
return count == 0;
main
メソッドで、カッコが正しく対応している文字列、閉じカッコの数が足りない文字列、開きカッコと閉じカッコの数は同じでも順番がよくない文字列の判定をしています。
public static void main(String[] args) { System.out.println(check("((test(data))test)" )); System.out.println(check("((test(data)test)" )); System.out.println(check("()test)data(test" )); }