文字列の処理

Java言語では文字列を扱うためには, Stringクラス を使う.String型で良く使うメソッドは以下のようなもの である.
length()
String の長さを返す。
charAt(int)
指定されたインデックスの文字を返す。
concat(String)
この形ではあまり使わないが,「string1 + string2」は string1.concat(string2)の意味.
equals(Object)
同じ文字列かどうかをチェックする.引数は Objectクラス(すべてのク ラスの先祖クラス)だが,実用上はStringクラスの引数を与えることが多い.
indexOf(int)
指定された文字が最初に出現する個所のインデックスを返す.出現しな かった時は -1 を返す.
lastIndexOf(int)
指定された文字が最後に出現する個所のインデックスを返す.出現しな かった時は -1 を返す.
substring(int beginIndex,int endIndex)
部分文字列を返す。 部分文字列 は beginIndex (これを含む)と endIndex (これは含まれない)によって指定される。
valueOf(char)
文字一つからなる文字列を返す.
文字列操作をおこなう例として,Hangman というゲームを作ってみる. Hangmanは古くからある単語宛ゲームで,はじめはすべての綴りが隠れている が,文字を1つづつ入力して,その文字が単語中にあると現れる.ないとミス が1つとなり,7つ目のミスでゲーム終了となる.

まずは,単語表を作る.Unixには /usr/share/dict/words(ただし,センター のUnixサーバでは/usr/dict/words)というファイルがあって,代表的な単語の 綴りが以下のようにABC順に並んでいる.

Aarhus
Aaron
Ababa
aback
abaft
abandon
abandoned
abandoning
abandonment
abandons
abase
しかし,これを問題に使うのは, という点で問題があるため, を選んで,そのうち20個に1個程度を乱数で選んだファイルを作ってみる.
// 入出力関係のクラスを使うため, java.io.* をimport
import java.io.*;
// Randomクラスを使うため, java.util.* をimport
import java.util.*;

class SelWords {
    // 途中で IOException 例外が発生することがあるため,throws IOException 
    // と記述する.
  static void selectWords() throws IOException{
    FileReader fr=null;
    try{
      // まずは /usr/share/dict/words を開いてみる
      fr=new FileReader("/usr/share/dict/words");
    } 
    catch(FileNotFoundException e){
      // 失敗したら /usr/dict/words を開いてみる
      fr=new FileReader("/usr/dict/words");
    }
      // 更に,BufferedReaderを作成.変数 br に代入.
    BufferedReader br=new BufferedReader(fr);
      // 行入力した結果の単語を入れる変数 s の宣言
    String s;
      // 乱数の種になる Random クラスのインスタンスの生成
    Random r=new Random();
      // break が出現するまで無限ループ
    for(;;){
        // 入力用 BufferedReaderであるbrから一行入力.ファイルの終端に
        // 来たときは null となるので,ループから抜ける.
      if((s=br.readLine())==null)
        break;
        // 読み込んだ単語の長さが8文字以上で,
      if(s.length()>7 && 
           // 0番目の(最初の)文字が,英小文字(lower case)で
         Character.isLowerCase(s.charAt(0)) && 
           // 整数の乱数を1つ取ってきたものがたまたま20で割りきれた場合は,
         r.nextInt(20)==0)
         // そのl単語を出力する
        System.out.println(s);
    }
     // 入力ファイルを close する.
    br.close();
  }
   // IOExceptionは本体の try-catch文 で捕まえてしまうので,
   // throws IOExxceptionは不要
  public static void main(String args[]){
    try {
      selectWords();
    } catch (IOException e){
      System.out.println(e);
    }
  }
}
上のプログラムをコンパイルして,
  java SelWord > words
と実行すると,wordsというファイルに1000単語程度が選択されて残る.

プログラム中に出てくるRandom パッケージ java.util に含まれるクラスで,擬似乱数系 列を生成する.

  Random r=new Random();
として,インスタンスを作ってから,r.nextInt(n) を呼び出すと,0-(n-1)の範囲の 整数の擬似乱数を生成する.
Character.isLowerCase(s.charAt(0))
は,s.charAt(0) で文字列sの0番目(最初の)文字を取っていて, Characterの isLowerCase()を呼び出すことによって,英小文字であ ることをチェックしている.

上で作られたファイルが /home/ktanaka/java/words だと仮定して, Hangmanのプログラムを書いてみる.クラス名は Hangman とする.まずは,擬 似乱数を使って適当な単語を1つ取ってくるメソッドgetRandomWordを書いてみ る.

簡単に書くために,一度,行数を数えてから乱数で何行目を正解にするかを決 める.そのあと,余分な行を読み飛ばしてから,一行を読んで記憶して返す. 関連するデータフィールド,メソッドは以下のようになる.

    // 先ほど作った辞書ファイルのファイル名の宣言
  static String dictFileName="/home/ktanaka/java/words";
  String getRandomWord() throws IOException{
    BufferedReader br=new BufferedReader(new FileReader(dictFileName));
    int lineCount;
    for(lineCount=0;;lineCount++){
      if(br.readLine()==null)
        break;
    }
    br.close();
    Random r=new Random();
    int selectedLine=Math.abs(r.nextInt()) % lineCount;
    br=new BufferedReader(new FileReader(dictFileName));
    for(lineCount=0;lineCount< selectedLine;lineCount++){
      br.readLine();
    }
    String result=br.readLine();
    br.close();
    return result;
  }
Hangman のプログラムを作る際に,Hangmanクラスのインスタンスを作るかど うかで設計が分かれるが,ここでは作るという方針でやってみる.mainは, Hangman クラスのインスタンスを作って,startGameを呼び出すだけにする.
  public static void main(String args[]){
    Hangman hangman=new Hangman();
    hangman.startGame();
  }
インスタンスのデータフィールドとして,正解の文字列 result, キーボード 入力のストリームsi, これまでに試した文字をまとめて文字列にした guessed などを作る.
  String result;
  BufferedReader si;
  String guessed;
他に,有用なメソッドとして以下のようなものも定義する.
boolean isGuessed(char c)
cがすでに解答した文字かどうかをチェック
boolean isAllGuessed()
正解文字列 result 中の文字がすべて guessed に含まれるかどうか?
String resultToString()
正解文字列 result 中の文字をまだ解答していない文字は「_」に変換して表示
String failString(int failCount)
hangman のゲーム名の由来通り,失敗の回数が多くなると,hangman の 姿がはっきりと表示されるように表示用文字列を生成する.
以上にもとづいて書いたプログラムは以下のようになる.
import java.io.*;
import java.util.*;

class Hangman {
  static String dictFileName="/home/ktanaka/java/words";
  String result;
    // キーボードから入力するための BufferedReader クラスの変数si の宣言
  BufferedReader si;
    // これまでに,guess した文字の列
  String guessed;
  String getRandomWord() throws IOException{
    BufferedReader br=new BufferedReader(new FileReader(dictFileName));
    int lineCount;
    for(lineCount=0;;lineCount++){
      if(br.readLine()==null)
        break;
    }
    br.close();
    Random r=new Random();
    int selectedLine=Math.abs(r.nextInt()) % lineCount;
    br=new BufferedReader(new FileReader(dictFileName));
    for(lineCount=0;lineCount< selectedLine;lineCount++){
      br.readLine();
    }
    String result=br.readLine();
    br.close();
    return result;
  }
    // Hangman クラスのコンストラクタ
  Hangman(){
    try {
        // キーボード入力用の BufferedReader の作成
      si=new BufferedReader(new InputStreamReader(System.in));
        // 解答の作成
      result=getRandomWord();
    } catch (IOException e){
        // 例外が起きたときは,例外の内容を表示して,
      System.out.println(e);
        // プログラムを終了する.
      System.exit(1);
    }
      // まだ何も guess していないので,空文字列を入れておく
    guessed="";
  }
    // ある文字 c が,guessed に含まれているかどうかを判定するインスタンスメソッド.
  boolean isGuessed(char c){
      // Stringクラスのメソッド indexOf は,文字が文字列に含まれないと
      // きは -1 を返す.
    if(guessed.indexOf(c)!= -1) return true;
    else return false;
  }
    // guessed に文字 c を加える.
  void guess(char c){
      // String.valueOf(c)で文字1文字からなる文字列ができるので,
      // +=で文字列の最後に付け加えることができる.
    guessed+=String.valueOf(c);
  }
    // result中の文字すべてが,guessed に入っているかどうか?
  boolean isAllGuessed(){
    int i;
      // resultの長さまでの間
    for(i=0;i< result.length();i++){
        // resultの i 番目の文字が,まだ guess されていない場合は 
      if(!isGuessed(result.charAt(i)))
          // false(偽) を返す
        return false;
    }
      // すべての文字が guessed中に含まれているので true を返す.
    return true;
  }
    // result の中で,guess されていない文字は「_」に置き換えた文字列を作る
  String resultToString(){
    int i;
    char c;
      // 結果は文字列 r にためていく
    String r="";
      // resultの長さまでの間
    for(i=0;i< result.length();i++){
        // resultの i 番目の文字が,まだ guess されている時は
      if(isGuessed(c=result.charAt(i))){
        // その文字をr に付け加え
        r+=String.valueOf(c);
      }
      else{
        // そうでないときは「_」をr に付け加え
        r+="_";
      }
    }
      // 最後に r を返す.
    return r;
  }
    // 失敗したした回数が failCountの時に,表示すべき絞首台の文字列
  String failString(int failCount){
      // 回数によって変わる部分を文字列の配列にしてある.見易いように
      // 文字列は行の切目で折り返してある.
    String [] failStr={
      "     |      \n"+
      "     |      \n"+
      "     |      \n"+
      "     |      \n",

      "     |    O \n"+
      "     |      \n"+
      "     |      \n"+
      "     |      \n",

      "     |    O \n"+
      "     |    | \n"+
      "     |      \n"+
      "     |      \n",

      "     |    O \n"+
      "     |    | \n"+
      "     |    | \n"+
      "     |      \n",
   
      "     |    O \n"+
      "     |    | \n"+
      "     |    | \n"+
      "     |   /  \n",

      "     |    O \n"+
      "     |    | \n"+
      "     |    | \n"+
      "     |   / \\\n",

      "     |    O \n"+
      "     |   /| \n"+
      "     |    | \n"+
      "     |   / \\\n",

      "     |    O \n"+
      "     |   /|\\\n"+
      "     |    | \n"+
      "     |   / \\\n"};
      // failCountによって,不変な部分を連結して返す.
    return 
      "      _____\n"+
      "     |    |\n"+
      failStr[failCount]+
      "   __|_____\n"+
      "   |      |___\n"+
      "   |_________|";
  }
    // ゲームの本体
  void startGame(){
    int failCount=0;
    char c;
    String s="";
     // 失敗が7回にならない間
    while(failCount<7){
        // 絞首台の表示
      System.out.println(failString(failCount));
        // 正解のうち guess した部分だけの表示
      System.out.println(resultToString());
        // 入力促進用のプロンプトの表示
      System.out.print(" Guessed "+guessed+" : Guess? "); 
      System.out.flush();
      try{
          // 標準入力siから一行入力して変数 s に入れる.
        s=si.readLine();
      } catch(Exception e){
        System.out.println(e);
        System.exit(1);
      }
        // 文字列の長さ1でなかったり,小文字でなかったり,すでに
        // 解答済の文字だったときには
      if(s.length()!=1 || !Character.isLowerCase(c=s.charAt(0)) || 
         isGuessed(c)){
          // 失敗の数は増やさず,エラーを表示してループの最初に戻る.
        System.out.println("Guess an unguessed lowercase character");
        continue;
      }
        // guessed に1加える
      guess(c);
        // resultの中に文字 c が出てこなかった場合
      if(result.indexOf(c)== -1){
          // failCountを増やす.
        failCount++;
      }
        // もしも,result中のすべての文字が guess された時は
      else if(isAllGuessed()){
          // 正解を表示して終了
        System.out.println("Congraturation ");
        System.out.println(resultToString());
        System.exit(0);
      }
    }
      // 7回失敗した時は,絞首台を表示して,正解も表示
    System.out.println(failString(failCount));
    System.out.println("The word is "+result);
  }
  public static void main(String args[]){
    Hangman hangman=new Hangman();
    hangman.startGame();
  }
}
failStringメソッドのfailStrの定義のところで,「\\」というのがいくつ も出てくる.これは,文字列中の「\」はエスケープ文字として特別な意味を 持つ,たとえば「\n」は改行を表すので,「\」自体を表すために「\\」と2つ 重ねているということである.
次に進む