練習問題

1. Table の作成

Integer型の2次元配列のようなデータ構造Tableを作成してください. すなわち,以下のプログラムが実行できるようなプログラムを作成してください.

public class TableRunner {
    void run() {
        Table table = new Table();
        table.setSize(3, 4);
        table.set(0, 0, 10);
        table.set(0, 1, table.get(0, 0) + 10);
        table.set(0, 2, table.get(0, 1) + 10);
        System.out.printf("%d行%d列の2次元配列%n", table.rowCount(), table.columnCount());
        table.print();
    }
    // mainメソッドは省略.
}
$ java TableRunner
3行4列の2次元配列
 10  20  30   0
  0   0   0   0
  0   0   0   0

ヒント

作成するメソッドとその内容

  • Table クラスでは,setSize, getsetrowCountcolumnCountprintメソッドを用意すると良いでしょう.
  • 値を格納するために,Integer型の二次元配列ではなく,HashMap<Integer, HashMap<Integer, Integer>>型の変数mapを利用しましょう.
  • setSizeではサイズが指定されます.
    • 指定された値は,rowCountcolumnCountでそれぞれ返り値として利用しますので,フィールド変数に代入しておきましょう.
  • setrowcolumn,値(value)が指定されます.
    • mapからrowHashMap<Integer, Integer>submapとします)を取得します.
    • submapnullなら新たな実体を作成しましょう.
    • submapcolumnをキー,valueをバリューとしてペアを追加します.
    • maprowをキー,submapをバリューとしてペアを追加します.
    • ただし,指定された rowcolumnがそれぞれ0以上,rowCountcolumnCount以下であることを確認しましょう.
      • もし,範囲を超えていれば,IndexOutOfBoundsExceptionを投げてください(具体的なコードはコード断片をご覧ください.).
  • getrowcolumnが指定されます.
    • mapからrowHashMap<Integer, Integer>submapとします)を取得します.
    • submapnullなら0を返します.
    • そうでなければ,submapからcolumnをキーとしてバリューを取得し,返します.
      • なお,バリューがnullの場合は,0を返すようにしておきましょう.
    • ただし,指定された rowcolumnがそれぞれ0以上,rowCountcolumnCount以下であることを確認しましょう.
      • もし,範囲を超えていれば,IndexOutOfBoundsExceptionを投げてください(具体的なコードはコード断片をご覧ください.).

コード断片

// import文
public class Table{
    HashMap<Integer, HashMap<Integer, Integer>> map = new HashMap<>();
    // その他のフィールド

    void setSize(Integer rowCount, Integer columnCount){
        // ...
    }
    void checkRange(Integer row, Integer column) {
        if(row < 0 || row >= rowCount() || column < 0 || column >= columnCount()){
            throw new IndexOutOfBoundsException(String.format(
                "size (%d, %d), index (%d, %d)", rowCount(), columnCount(), row, column));
        }
    }

    Integer get(Integer row, Integer column){
        checkRange(row, column);
        // ...
    }
    void set(Integer row, Integer column, Integer value){
        checkRange(row, column);
        // ...
    }
    Integer rowCount(){
        // ...
    }
    Integer columnCount(){
        // ...
    }
    void print(){
        for (Integer i = 0; i < rowCount(); i++){
            for (Integer j = 0; j < columnCount(); j++){
                System.out.printf(" %3d", get(i, j));
            }
            System.out.println();
        }
    }
}

出力例

問題文で示したTableRunnerの通りです. Table.java自体は mainメソッドを用意しなくて構いません.

2. CSVデータの格納

ここでは,郵便番号から住所の検索を行うプログラムを作成します.

  1. 郵便番号データを用意してください.
    • 郵便番号データダウンロードページから自分の出身地の郵便番号データをダウンロードしてください.
    • ダウンロードしたzipファイルを解凍してください.
    • 解凍してできたファイルを zipcode.csv にファイル名を変更してください.
    • ダウンロードしたファイルの文字コードは ShiftJIS になっています.utf-8 でなければJavaでは文字化けを起こしますので,変換しておいてください.
      • iconvコマンドがインストールされているなら,ターミナルで次のように入力しましょう.
        • iconv -t utf-8 zipcode.csv > zipcode2.csv
          • iconv --to-code utf-8 zipcode.csv > zipcode2.csv でも良い.
        • mv zipcode2.csv zipcode.csv
      • 各種エディタでも文字コードを変換できます.
        • Emacsであれば,ファイルを開いて,Ctrl+x RET f と入力すると変換したい文字コードを聞かれますので,utf-8 と入力してください.
        • Atomであれば,AtomでShift-JISのCSVデータをUTF-8に変換するを参照してください.
  2. 郵便番号を読み込み,検索を行うプログラムを作成してください.
    • クラス名は ZipCodeとしてください.
    • コマンドライン引数で与えられた郵便番号に対応する住所を出力してください.

ヒント

ベースとなるプログラム

例題1. お弁当の料金表が参考になるでしょう. 初期化処理(initializeメソッド)でzipcode.csvを読み込み,HashMap型の変数に 郵便番号と対応した住所を追加(put)しましょう.

その後,検索処理を行います.検索処理はsearchAndPrintメソッドとほぼ同じで良いでしょう. キーとバリューそれぞれの型は何が良いかを考えて作成しましょう.

ダブルクォートの削除

ダウンロードした csv ファイルは,各カラムがダブルクォートで囲まれています. これを削除するには,以下のメソッド(stripQuote)を使うと良いでしょう.sampleCodeメソッド はstripQuoteの使い方の例です.sampleCodeメソッドにあるように,splitで 得られた配列の各要素を順にstripQuoteに渡すことで, ダブルクォートを除いた部分を取り出しています.

    String stripQuote(String item){
        if(item.matches("\".*\"")){
            return item.substring(1, item.length() - 1);
        }
        return item;
    }
    void sampleCode(){
        String original = "\"クォートで囲まれた文字列\"";
        String value = this.stripQuote(original);
        // originalには「"クォートで囲まれた文字列"」が代入されており,
        // valueには「クォートで囲まれた文字列」が代入される.
    }

なお,matches メソッドは与えられた正規表現(Regular Expression)にマッチするかを判定するメソッドです.

出力例

$ java ZipCode 6038035 6038047 1000001
6038035: 京都市北区上賀茂朝露ケ原町
6038047: 京都市北区上賀茂本山
1000001: 見つかりませんでした

100-0001 は東京都の郵便番号ですので,京都府の郵便番号表では見つかりません. そのため,上記のように「見つかりませんでした」と出力されています.

3. 電話帳の作成

以下の仕様に従った名前と電話番号のペアを管理する電話帳を作成しましょう.

  • クラス名はPhoneBookとしてください.
  • 起動するとコマンド入力待ちとなります.
    • 以下のコマンドを入力することによって,データの更新が行われる.
    • add 名前 電話番号
      • 電話帳に名前と対応する電話番号を追加する.
    • list
      • 登録された名前と電話番号の一覧を表示する.
    • find 名前
      • 電話帳に名前が存在すれば名前と電話番号を表示する.
      • 電話帳に名前が存在しなければ何も表示しない.
    • remove 名前
      • 電話帳から名前のデータを削除する.
      • 電話帳に名前のデータが存在しなければ何も行わない.
    • exit
      • 電話帳を終了する.
  • 入力において,文法エラーは起きないものとします.
  • 標準入力から1行読み込むには,SimpleConsole.javaを利用してください.
    • SimpleConsole.javaをここからダウンロードし,プログラムと同じディレクトリに置いてください.
    • 入力した文字列をスペースで区切るには,String型のsplitメソッドを利用しましょう.
      • String[] items = line.split(" ");
    • SimpleConsole.javaは一切変更してはいけません.
      • 同様に,PhoneBook.javaには,SimpleConsole.java のコードを貼り付けてはいけません.

ヒント

SimpleConsole の使い方.

SimpleConsole console = new SimpleConsole();
String line = console.readLine();

利用する前に,new で実体を作成してください. その後,得られた実体(上記の例では console)に対して readLine メソッドを呼び出すと1行読み込むことができます.

出力例

> list
> add tamada 090-1111-1111
> find tamada
tamada 090-1111-1111
> find akiyama
> add akiyama 090-2222-2222
> list
tamada 090-1111-1111
akiyama 090-2222-2222
> remove akiyama
> find akiyama
> add tamada 090-1111-2222
> list
tamada 090-1111-2222
> exit

> で始まる行が入力を表しています. 最初は電話帳に何も入力されていないため,listコマンドを入力しても何も出力されません. データをaddコマンドで追加していくことで,結果が変わってきます.