Aikの技術日記

技術的な進捗とか成果とかを細々と投稿するブログです。時々雑記も。

Javaで書く括弧検証プログラム その1

その2(その1からの改良Ver)はこちら
その3(複数文字括弧対応Ver)はこちら

括弧検証プログラムを作ろう

この度筆者は、自分が所属してるチーム内での課題として、括弧検証プログラムを作る事になりました。

スクリプト言語は禁止との事で、どれで書こうか悩みましたが…。
チーム内で近々Javaプログラミング教室があるらしいので、予習も兼ねてJavaで作る事にしました。
※ちなみに筆者のJavaレベルは(ほぼ)0です

ついこの間Java開発環境を整えるべくEclipseを導入しましたが…。

簡単なプログラムならIDE無しに、Bracketsでコードを書き、ターミナルからコンパイルしていく方針にしました。

下記のサイトを見ながらJavaについて勉強勉強…!
www.javadrive.jp

実装に向けて

今回のプログラムはスタックで挑みたいと思います。

AIZU Online JUDGEの類似問題ではスタックを使わないアルゴリズムもありましたが…。
スタックを知ってしまうと「スタックの方が格好良くできる!」と思ってしまいますね。

また、プログラムとして一先ず実装したい機能は下記の通りです。

  • 外部のファイルを読み込む
  • 括弧対応がなされているかをスタックを用いてチェック
  • (出来れば)プログラム実行時にファイルパス入力をして任意ファイルを読み込ませる

Javaの事をほぼノー知識で挑んだので、色々と苦労しましたが…。
C言語だと、スタック構造を自力で書かないといけないのでね。
それに比べたら安いものですわ…。

さて、次はいよいよコード書きです!

書きながら気づいた事

プログラムを書く時に気づいた、ちょっとした小ネタなどを記します。

コンパイルについて

私は色んなファイルをついついフォルダ単位でまとめる癖があり…。
今回も以下のような感じでフォルダ管理していました。

  • sampleフォルダ: 恒久的に使える OR 入門サイトにあったソースコードのコピペをしたプログラム置き場
  • exフォルダ: 今回の課題プログラム OR 自力で書いたソースコードプログラム置き場

ただ、プログラミングしてる内に、「exのフォルダに居るけどここからsampleにあるプログラムをコンパイルしたいなー」となる事があってですね。

その状況下でコマンドを叩くと、こんなエラーが出まして…。

Exception in thread "main" java.lang.NoClassDefFoundError: ...

何じゃこりゃと思い調べてたら、「君が作業してるフォルダにこのファイル無いからコンパイル出来ないよ」って事でした。
※参照記事はこちら

異なるフォルダを跨いでコンパイル出来ないんですな…。

文字リテラル

大した事ない知識ですが、備忘録的な意味も込め、メモしておこうかと。
…の前にリテラルの言葉が分からないので検索。

リテラルとは、プログラムのソースコードにおいて使用される、数値や文字列を直接に記述した定数のことである。変数の対義語であり、変更されないことを前提とした値である。
リテラルの意味や定義 Weblio辞書

この情報も助かりましたが、個人的には今回勉強に使ったサイト内にあった、

リテラル」 とは 「の値」
Java | 文字リテラルと文字コード

…が凄く分かり易かったので、この形で覚えていこうと思います。

また、Javaにおいて、プログラム内で文字リテラルを扱う際はシングルクォーテーション(')で囲って表記するそうな。
こんな感じ→ 'a' 'あ'

また、文字リテラルはその文字の値を示すものなので、2文字以上をシングルクオーテーションで囲った時はエラーになるみたいです。
こんな感じ→ 'ab' 'あい'

以前、ダブルクオーテーションで囲った文字とシングルクオーテーションで囲った文字の違いを耳にした気がしたのですが…。
結局自分の中であやふやになってたので、今回の勉強で整理出来ました。

(ちなみにダブルクオーテーションで囲った文字列は、「文字リテラル」と言うそうです)

Javaでスタック→Dequeを使おう

JavaにもStackクラスはあるそうですが、どうやら古いらしく、今ではDequeクラスを使うように推奨されているそうです。

※参考記事:
Deque (Java Platform SE 8)
Stack (Java Platform SE 8)

そしてDequeはキューにもスタックにも両方使えるものらしいです。
下記の記事に大変お世話になりました。

d.hatena.ne.jp ※↑DequeクラスとStackクラスの比較を詳しく紹介されています。

versionによるsplitメソッドの挙動差異

splitメソッドで1文字ずつ文字列を切り出す際、Java7までは…

String[] array = "hoge".split("");
  →["", "h", "o", "g", "e"]

…と言うように、配列の先頭には空白が入る様になっていましたが…。
これがJava8からは…

String[] array = "hoge".split("");
  →["h", "o", "g", "e"]

…と、配列の先頭から切り出した文字が入る様になったらしいです。
ちょうど自分の環境に"JDK1.6.0"と"JDK10"両方あったので試してみました。

$ echo $PATH /Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home/bin
$ java sample
hoge
["", "h", "o", "g", "e", ]
$ echo $PATH /Library/Java/JavaVirtualMachines/jdk-10.jdk/Contents/Home/bin
$ java sample
hoge
["h", "o", "g", "e", ]

確かに変わっています。
バージョン管理が大事になってきそうですね…。

完成しました!

色々やって何とか完成しました!
超長ったらしいコードになり…果たしてこれは良いコードだと言えるのでしょうか。

とりあえずソースコードだけ貼り付けておきます。
※長ったらしいので折りたたんでおきました

ソースコードを展開する

  // 作成日 18.03.21
  import java.io.File; // ファイルオブジェクト宣言用  
  import java.io.FileReader; // ファイルの読込屋さん  
  import java.io.BufferedReader; // FileReaderの強化版,FileReaderの機能を利用し更に強くなる  
  import java.io.FileNotFoundException; // 例外処理用  
  import java.io.IOException; // 例外処理用  

  import java.util.Deque; // スタックオブジェクト宣言用  
  import java.util.ArrayDeque; // Dequeの実装クラス

  class checkkakko1{
    // 検査する括弧の種類を定義(アスキーコード)
    public static final char bracket1_open = 40; // "("
    public static final char bracket1_close = 41; // ")"

    public static void main(String args[]){
      // 処理時間計測用タイマーセット
      long start = System.nanoTime();

      // 読み込みたいファイルのパス
      String filename = args[0];

      System.out.println("************************");
      System.out.println("Check of Brackets");
      System.out.println("File name - " +filename);
      System.out.println("");

      // ファイルの読み込み
      try{
        // 指定パスのファイルを読み込む
        File file = new File(filename);
        // ファイルが読み込めるかチェック、読みこめたら処理続行
        if (checkBeforeReadfile(file)){
          BufferedReader br = new BufferedReader(new FileReader(file));

          String str;
          Deque<Character> bracketsStack = new ArrayDeque<Character>();

          // 一行ごとに文字を読み込み
          while((str = br.readLine()) != null){
            // 1文字ずつchar型に変換、配列へ
            char[] cArray = str.toCharArray();
            for (char c : cArray) {
                //System.out.println(c);
                if(c == bracket1_open || c == bracket1_close){
                  bracketsStack.addFirst(c);
                }else{
                  //System.out.println("non-bracket");
                }
            }
            //System.out.println("------------");
            //System.out.println(str);
          }
          if(judgeBracket(bracketsStack)){
            System.out.println("");
            System.out.println("************************");
            System.out.println("Brackets Check -> Clear");
            System.out.println("************************");
          }else{
            System.out.println("");
            System.out.println("************************");
            System.out.println("Brackets Check -> failed");
            System.out.println("************************");
          }
          //System.out.println("------------");
          //System.out.println(bracketsStack);

          br.close();
        }else{ // ファイルが読みこめなかったら処理はしない
          System.out.println(filename+ "is Not Found");
        }
      }catch(FileNotFoundException e){
        System.out.println(e);
      }catch(IOException e){
        System.out.println(e);
      }

      // 処理速度計測用タイマーストップ
      long end = System.nanoTime();
      System.out.println("");
      System.out.println("Program Time:" + (end - start) / 1000000f + "ms");
    }

    /**
    * 括弧対応を2つのスタックを用いて精査する
    * @param Stack: 括弧のみが入った精査する用のスタック
    **/
    private static boolean judgeBracket(Deque Stack){
      Deque<Character> bracketsStackJudge = new ArrayDeque<Character>();
      while(!Stack.isEmpty()){
        // スタックから1つ取り出す
        char p = ((Character)Stack.removeFirst()).charValue();
        //System.out.println("pop is "+ p);

        if(p == bracket1_close){
          bracketsStackJudge.addFirst(p);
        }
        else if(p == bracket1_open){
          // 開き括弧があるのに対応する閉じ括弧がない->エラー
          if(bracketsStackJudge.isEmpty()){
            //System.out.println("bracket-close not found");
            return false;
          }else{ // bracketsStackJudgeから対応する閉じ括弧を取り出す
            char pp = ((Character)bracketsStackJudge.removeFirst()).charValue();
          }
        }
      }
      // 両方とものスタックが空な事を確認する
      if(bracketsStackJudge.isEmpty()){
        return true;
      }else{
        return false;
      }
    }

    /**
    * 指定したファイルが存在し、かつそれが読み込み出来るファイルかチェック
    * @param file: チェックしたいFile型のオブジェクト
    **/
    private static boolean checkBeforeReadfile(File file){
      if (file.exists()){
        if (file.isFile() && file.canRead()){
          return true;
        }
      }
      return false;
    } 
  }

ソースコードのリーディングを碌にやってないので、こんな風になるのでしょうか。
今後はそちらの方もやりながら頑張ります!