Java演習問題 – エンジニアの入り口 https://eng-entrance.com 「エンジニアの入り口」は、プログラミング入門やエンジニアリング入門の知識が満載の初心者のための勉強サイトです。プログラミングやサーバ、ネットワークの基礎知識や勉強方法を習得できます。 Sat, 22 Apr 2017 03:03:00 +0000 ja hourly 1 https://wordpress.org/?v=5.2.19 【入門者向け】Java演習問題:オブジェクト指向編 https://eng-entrance.com/java-question-objective https://eng-entrance.com/java-question-objective#respond Sun, 26 Mar 2017 03:50:09 +0000 http://eng-entrance.com/?p=5168 このページではJavaオブジェクト指向の演習問題をご用意してみた。

時間があるときにスキルアップとして、トライしてみていただければと思う。

Javaオブジェクト指向の演習問題

オブジェクト指向とはなんぞや?という方は下記を先に確認いただくのが良い。

解答方法

・入力値は問題で定められた規則に従い、正しく入力されるものとし、入力ミスなどは考慮しなくてよい。

問題1(継承)

以下のプログラムを参考にし、以下のAbstractAutomobileクラス(自動車クラス)を継承したMyCarクラスを作成しなさい。

継承元となるクラス

public abstract class AbstractAutomobile {

    /**
     * 車種区分
     */
    protected enum CarType {
        LIGHT_CAR,        //軽自動車
        STANDERD_CAR,    //普通者
        MEDIUM_CAR,        //中型車
        LARGE_CAR,        //大型車
        OVERSIZE        //特大車
    } ;



    /**
     * コンストラクタ
     */
    protected AbstractAutomobile(){

    }
    /**
     * コンストラクタ
     * @param carType 車種区分
     */
    protected AbstractAutomobile(CarType carType){
        this.setCarType(carType) ;
    }

    /**
     * 車種区分
     */
    private CarType carType;

    /**
     * @return 乗車定員
     */
    public abstract int getCapacity();


    /**
     * @return 車種(モデル)
     */
    public abstract String getCarModel();

    /**
     * @return 車種区分
     */
    public CarType getCarType() {
        return carType;
    }
    /**
     * @param carType 車種区分
     */
    public void setCarType(CarType carType) {
        this.carType = carType;
    }


    /**
     * @return 車種区分(文字列)
     */
    public String getCarTypeString(){
        String typeName = "" ;
        if(this.carType.equals(CarType.LIGHT_CAR)){
            typeName = "軽自動車";
        }else if(this.carType.equals(CarType.STANDERD_CAR)){
            typeName = "普通者";
        }else if(this.carType.equals(CarType.MEDIUM_CAR)){
            typeName = "中型車";
        }else if(this.carType.equals(CarType.LARGE_CAR)){
            typeName = "大型車";
        }else if(this.carType.equals(CarType.OVERSIZE)){
            typeName = "特大車";
        }
        return typeName ;
    }

MyCarクラスは、以下の条件とする。

  • 車種区分:普通者
  • 車種(モデル):自家用ワンボックス車
  • 乗車定員:7人

作成したクラスは、以下のMainクラスを使用して実行する

public class Main {
    public static void main(String[] args) {
        MyCar car = new MyCar();
        System.out.println("車種(モデル)は" + car.getCarModel() + "です");
        System.out.println("乗車定員は" + car.getCapacity() + "人です");
        System.out.println("車種区分は" + car.getCarTypeString() + "です");
    }
}

実行例:

  • 車種(モデル)は自家用ワンボックス車です
  • 乗車定員は7人です
  • 車種区分は普通者です

問題2(インターフェースの実装)

4つの項目を持つCSV(カンマ区切り)ファイルを読み込み、指定された順序に並び替えて表示するプログラムを作成しなさい。

4つの項目はそれぞれ

・日付

・最高気温

・最低気温

・平均気温

となっており、表示する順序は

・最高気温の高い順

・最低気温の高い順

・平均気温の高い順

・日付の古い順

の4つの順序の優先順位とし、表示時の列順序も同様とする。

なお、読み込みファイルは「data」を使用すること。

また、プログラムの作成においては、並び替えの順序判定を行う為のTemperature Comparatorクラスを作成し、Collectionsクラスによるソートを利用するものとする。

実行例(「data.csv」を利用した場合)

 

32,19.7,24.9,2016/10/4
31.3,21.4,26,2016/10/6
27.7,17,21.9,2016/10/20
27.6,18.6,23.1,2016/10/2
26.5,20.1,22.7,2016/10/3
26.4,21.5,24,2016/10/5
26.3,16.7,20.9,2016/10/18
25.7,18.6,22.3,2016/10/9
25.7,12.6,19.1,2016/10/26
25.3,18.2,21.2,2016/10/8
24.9,19,21.4,2016/10/7
23.9,18.4,20.8,2016/10/19
23.3,13.2,19,2016/10/27
23.2,13.7,17.9,2016/10/12
22.9,12.7,17.4,2016/10/16
22.7,11.8,16.7,2016/10/15
22.3,14.7,18.1,2016/10/23
21.8,17.6,19.7,2016/10/1
21.4,15.4,18.2,2016/10/21
20.8,11.6,15.5,2016/10/29
20.1,16.2,17.6,2016/10/11
19.6,17.4,18.4,2016/10/10
19.5,15.7,17.5,2016/10/17
19.5,12.6,15.6,2016/10/14
19.1,11.9,14.9,2016/10/24
19.1,9.8,14.3,2016/10/31
18.3,14.7,16.7,2016/10/22
18.2,15.3,16.5,2016/10/13
17.2,10,14,2016/10/25
14.8,10.1,12.2,2016/10/28
13.5,9.6,11.9,2016/10/30

 

 

 

 

 

 

以下、回答

問題1 解答/解説

解答例

public class MyCar extends AbstractAutomobile {

    protected MyCar() {
        super(CarType.STANDERD_CAR);
    }

    @Override
    public int getCapacity() {
        return 7;
    }

    @Override
    public String getCarModel() {
        return "自家用ワンボックス車";
    }
}

オブジェクト指向に関する問題だ。

オブジェクト指向を学ぶ際に、例え話として車を表すCarクラスとバスやトラックなどを表すクラスの関係を用いることがある。実際にどのような形で使われるのかを確認する問題と考えていただければと思う。

抽象化

解答例を見て、コード量の少なさに驚いた方もいるかもしれない。

継承元となっているAbstractAutomobileクラスには各種メソッドが用意されており、実体となるMyCarクラスでは、その「差」になる部分だけを用意すれば良い。

また、AbstractAutomobileクラスは継承されることを前提とした「abstract」で定義されており、継承したクラスが用意すべきメソッド(getCapacity、getCarModel)を指定している。

このように「abstract」が指定されたクラスやメソッドを抽象クラス、抽象メソッドと呼び、このようなクラス・メソッドを定義する事を「抽象化」と呼ぶ。

抽象化は実体を宣言するのでは無く、継承先のクラスに実装を求めるものだ。

Javaをはじめとするオブジェクト指向言語では、この抽象化によって、オブジェクトにバリエーションを持たせる手法が多い。

最も身近に利用されるのが、java.io.InputStreamやjava.io.OutputStreamだろう。

InputStreamは、データ読み込みの為の基本的な機能を備えているが、その読み込む媒体によって実装を分けている。

ファイルを読み込む為にはFileInputStreamを用いるし、テキストデータであればStringBufferInputStream、バイナリデータであればByteArrayInputStreamを用いる。

いずれも、データを読み込む為のread()メソッドを実装しており、InputStreamで用意したread(byte[] b)やread(byte[] b,int off,int len)などを利用する事も可能だ。

プログラム作成過程には、「プログラム設計」と呼ばれる、どのようなクラスやメソッドを作成すべきかを検討する段階があるが、オブジェクト指向言語を利用する場合には、抽象化と継承を効率よく利用し、機能の共通化を図るとスマートなソースコードが書けるだろう。

問題2 解答/解説

解答例

public class Main {
    public static void main(String[] args) {
        try{
            FileReader file = new FileReader("data.csv");
            BufferedReader buff = new BufferedReader(file);
            String text;
            ArrayList<String[]> tempList = new ArrayList<String[]>();
            while ((text = buff.readLine()) != null) {
                String data[] = text.split(",");
                tempList.add(data) ;
            }
            file.close();

①	        TemperatureComparator tc = new TemperatureComparator() ;
            Collections.sort(tempList, tc) ;
            for(String data[] :tempList){
                System.out.println(data[1] + "," + data[2] + "," + data[3] + "," + data[0]);

            }
        }catch(FileNotFoundException fnoe){
            fnoe.printStackTrace();
        }catch(IOException ioe){
            ioe.printStackTrace();
        }
    }
}

public class TemperatureComparator implements Comparator<String[]> {

    @Override
    public int compare(String[] o1, String[] o2) {
        //最高気温比較
        int maxTemp1 = (int)(Double.parseDouble(o1[1]) * 10);
        int maxTemp2 = (int)(Double.parseDouble(o2[1]) * 10);
        if(maxTemp1 < maxTemp2){
            return 1 ;
        }else if(maxTemp1 > maxTemp2){
            return -1 ;
        }else{
            //最低気温比較
            int minTemp1 = (int)(Double.parseDouble(o1[2]) * 10);
            int minTemp2 = (int)(Double.parseDouble(o2[2]) * 10);
            if(minTemp1 < minTemp2){
                return 1 ;
            }else if(minTemp1 > minTemp2){
                return -1 ;
            }else{
                //平均気温比較
                int avgTemp1 = (int)(Double.parseDouble(o1[3]) * 10);
                int avgTemp2 = (int)(Double.parseDouble(o2[3]) * 10);
                if(avgTemp1 < avgTemp2){
                    return -1 ;
                }else if(avgTemp1 > avgTemp2){
                    return 1 ;
                }else{
                    //日付を比較
                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
                    try {
                        Date date1 = sdf.parse(o1[0]);
                        Date date2 = sdf.parse(o2[0]);
                        if(date1.before(date2)){
                            return -1 ;
                        }else if(date2.before(date1)){
                            return 1 ;
                        }else{
                            return 0 ;
                        }
                    } catch (ParseException e) {
                        // TODO 自動生成された catch ブロック
                        e.printStackTrace();
                    }
                }
            }
        }
        return 0;
    }

}

interfaceの実装に関する問題だ。

まず、Mainクラスの処理だが、こちらはCSVファイルを読み込んでカンマ区切りの処理を行っているだけなので、詳細は割愛する。

注目すべきは①のTemperatureComparatorを生成している個所と、その次のCollections.sort()メソッドを呼び出している個所だ。

Collections.sort()メソッドは、Listオブジェクトの並び替えを行うためのメソッドで、Collectionsクラスの政的メソッド(static)として定義されている。

静的クラスなので、インスタンスを生成する必要なく利用可能だ。

このメソッドに対し、

  • 並び替えを行うリストオブジェクト
  • 並び替えのルール(順序付けの判定)を実装したTemperatureComparatorクラスのオブジェクト

を渡すことで、リストオブジェクトの中身を並び替えてもらうことが可能なのだ。

次に、TemperatureComparatorクラスのソースを確認してみよう。

まず、クラス宣言後半の、「implements Comparator」という記述に注目してもらいたい。implementsは実装するインターフェースを記述する際のもので、ここでは「Comparatorインターフェースを実装する」とう意味を表している。

インタフェースの実装とは何か?

ここで「インターフェースの実装」とは何かを整理しておこう。

簡単に言ってしまうと、インターフェースとはプログラムの実体部分を持たず、その実装されるクラス(ここではTemperatureComparatorクラス)にその実体部分を持つという事になる。

インターフェースとなるクラスでは、メソッドの型(引数や戻り値の型)だけを定めており、このメソッドを抽象メソッドと呼ぶ。

抽象クラスでは、そこに定められる抽象メソッドにはabstractの記述が必要だったが、インターフェースでは、そこに定められるメソッドはすべて抽象メソッドであるため、abstractの記述は不要だ。

Comparatorインターフェースにはcompare()メソッドとequals()が抽象メソッドとして定められている。

compare()メソッドでは、2つのオブジェクトを自身で定める基準に従って「どちらが大きいか」の順序付けを行う。

equals()メソッドでは、2つのオブジェクトが「等しい」とされるかどうかを判断する。

Comparatorインターフェースを実装するクラスでは、この2つのメソッドを用意しなければならない。

模範解答では、TemperatureComparatorクラスを

public class TemperatureComparator implements Comparator<String[]>

と定義しており、String配列を比較するルールを定めなければならない。

compare()メソッドでは、問題の指示に従い、

・最高気温の高い順

・最低気温の高い順

・平均気温の高い順

・日付の古い順

の4つの基準で大小を判定している。

この比較結果により、List<String[]>が並び替えられ、出力結果となっている。

また、観察力の鋭い方は、TemperatureComparatorクラスがequals()メソッドを実装していないことに気が付くだろう。

本来であればString配列を題意に従って比較し、同一であることを判断する為のequals()メソッドを実装するべきなのだが、ここでは省略している。

省略といっても、equals()メソッドが無いのではなく、TemperatureComparatorクラスは親クラスとなっているjava.lang.Objectクラスのequals()メソッドを継承しており、Comparatorインターフェースのequals()メソッドの実装としては、java.lang.Objectクラスのequals()メソッドが使用されることになる。

単なる並び替え処理の場合ではあまり問題にならないため、今回の解答例ではequals()メソッドを省略しているが、Comparatorを使用する一部のクラスでは、equals()メソッドを正しく実装しなければならないこともあるので注意してもらいたい。

「正しく実装」とは、compare()メソッドが「0」を返すケースと、equals()メソッドがtrueを返すケースが同じになるような実装を言う。

まとめ

このページではJavaオブジェクト指向の演習問題を2つご用意してみた。ぜひ回答を見る前にトライをしていただければ幸いだ。

]]>
https://eng-entrance.com/java-question-objective/feed 0
【入門者向けトレーニング】Javaの練習問題:知識編 https://eng-entrance.com/java-question-knowledge https://eng-entrance.com/java-question-knowledge#comments Sun, 30 Oct 2016 01:34:37 +0000 http://eng-entrance.com/?p=3188 このページではJavaの練習問題、特に知識が問われる問題をまとめて作成してみた。

Javaに対してどれくらい理解ができてきたかの試金石になるだろう。少々難しい部分もあるので、初心者の方はしっかり勉強してからトライしてみていただければと思う。

Javaの練習問題:知識編

それでは早速、練習問題に取り掛かっていこう。

解答方法

入力値は問題で定められた規則に従い、正しく入力されるものとし、入力ミスなどは考慮しなくてよい。

練習問題1(java.ioパッケージ テキストファイル入出力)

テキストファイルに記載された内容を表示し、異なるファイルに出力するプログラムを作成しなさい

  • 読み込むファイル名、出力するファイル名は、引数から取得するものとする。
  • テキストファイルはUTF-8形式で記述されたものとする。
  • 改行コードはCRLFを使用する。

実行例

java Main textA.txt textB.txt

入力ファイルの中身

Javaプログラムの実行結果
ABCDEFG
1234567890
あいうえお
ABCDEFG
1234567890

出力例

Javaプログラムの実行結果
ABCDEFG
1234567890
あいうえお
ABCDEFG
1234567890

練習問題2(java.ioパッケージ バイナリファイル入出力)

バイナリ形式のファイルを読み込み、異なるファイルに出力するプログラムを作成しなさい

ただし、読み込むファイル名、出力するファイル名は、引数から取得するものとする。

実行例

java Main imageA.png imageB.png

練習問題3(java.utilパッケージ Listの利用)

テキストファイルに記載された内容を1行ずつListに格納し、以下の処理を行うプログラムを作成しなさい。

  • 1行目から順に出力する
  • 最終行から逆順に1行目まで出力する
  • 文字列「あいうえお」が格納されている行番号を出力する(先頭行を1行目と数える)

なお、テキストファイルの入力については以下の条件とする

  • 読み込むファイル名、出力するファイル名は、引数から取得するものとする。
  • テキストファイルはUTF-8形式で記述されたものとする。
  • 改行コードはCRLFを使用する。

実行例

java Main textA.txt

入力ファイルの中身

Javaプログラムの実行結果
ABCDEFG
1234567890
あいうえお
ABCDEFG
1234567890

Javaプログラムの実行結果出力例

ABCDEFG
1234567890
あいうえお
ABCDEFG
1234567890
1234567890
ABCDEFG
あいうえお
1234567890
ABCDEFG
3行目

練習問題4(java.utilパッケージ Mapの利用)

テキストファイルに記載された内容を1行ずつMapに格納し、以下の処理を行うプログラムを作成しなさい。

なお、テキストファイルの入力については以下の条件とする

  • 読み込むファイル名、出力するファイル名は、引数から取得するものとする。
  • テキストファイルはUTF-8形式で記述されたものとする。
  • 改行コードはCRLFを使用する。
  • 1行には、空白区切りで2つの文字列が記述されており、一つ目をキー、2つ目を値として、Mapに格納する。

処理内容

  • すべてのキーと値を出力する
  • キー「TOKYO」の値を出力する

実行例

java Main textA.txt

入力ファイルの中身

IBARAKI 水戸市
TOCHIGI 宇都宮市
GUNMA 前橋市
SAITAMA さいたま市
CHIBA 千葉市
TOKYO 東京(新宿区)
KANAGAWA 横浜市

出力例

GUNMA : 前橋市
TOKYO : 東京(新宿区)
CHIBA : 千葉市
IBARAKI : 水戸市
SAITAMA : さいたま市
KANAGAWA : 横浜市
TOCHIGI : 宇都宮市
TOKYO=>東京(新宿区)

練習問題5(java.utilパッケージ Comparatorの利用)

問題4の入力に対して、以下の表示順序で出力するプログラムを作成しなさい。

出力順序

  • キーの文字列順序(アルファベット辞書順)で出力する
  • キー名の短い順とし、同じ長さの場合はアルファベット辞書順とする

実行例

java Main textA.txt

入力ファイルの中身

IBARAKI 水戸市
TOCHIGI 宇都宮市
GUNMA 前橋市
SAITAMA さいたま市
CHIBA 千葉市
TOKYO 東京(新宿区)
KANAGAWA 横浜市

出力例1

CHIBA : 千葉市
GUNMA : 前橋市
IBARAKI : 水戸市
KANAGAWA : 横浜市
SAITAMA : さいたま市
TOCHIGI : 宇都宮市
TOKYO : 東京(新宿区)

出力例2

CHIBA : 千葉市
GUNMA : 前橋市
TOKYO : 東京(新宿区)
IBARAKI : 水戸市
SAITAMA : さいたま市
TOCHIGI : 宇都宮市
KANAGAWA : 横浜市

 

 

 

練習問題の解答

いかがだっただろうか? すんなり書けたという人も、なかなか難しかったという人もいるだろう。

ここからはJava練習問題の解答をお伝えする。できれば解いてから見てみていただきたい。

練習問題1 解答/解説

解答例

public class Main {
    public static void main(String[] args) {
        try{
	        FileReader file = new FileReader(args[0]);//①
                BufferedReader buff = new BufferedReader(file);
	        FileWriter writer = new FileWriter(args[1]);//②
               String text;
               while ((text = buff.readLine()) != null) {
                  System.out.println(text);
               writer.write(text);
	       writer.write("\r\n");//③
            }
	    file.close();//④
            writer.close();
	}catch(FileNotFoundException fnoe){//⑤
            fnoe.printStackTrace();
	}catch(IOException ioe){ //⑥
              ioe.printStackTrace();
        }
    }
}

  1. テキストファイルの読み込みには、FileReaderおよびBufferedReaderを利用する。
  2. テキストファイルの出力には、FileWriterを利用する。
  3. 改行コード「CRLF」を出力する場合は、プログラム中では「\r\n」を出力する。
  4. 入力ファイル、出力ファイル共に、最後にclose()を忘れてはならない。
  5. FileReaderのコンストラクタでは、入力ファイルが見つからない場合にFileNotFoundExceptionがThrowされる。
  6. readLine()メソッドでのファイルの読み込みや、write()メソッドでのファイル出力時のエラーは、IOExceptionがThrowされる。

javaによるファイル入出力の問題だ。

基本的には「ファイルを開く」「ファイルを読む、書き込む」「ファイルを閉じる」の順に実行しているだけだが、いくつか注意しなければならないポイントがある。

try/catchによる例外の捕捉

ファイルを取り扱う為のメソッドの多くは、あらかじめ例外が定義されており、try/catchでの例外処理を必要とする。

例外が発生するケースとしては

  • 読み込むためのファイルが存在しない
  • ファイルが開けない(ファイルのアクセス権など)
  • ファイルが読めない(ディスクの不良など)
  • ファイルに書き込めない(ディスクの空きが無いなど)

などが考えられる。

また、この問題では

  • テキストファイルはUTF-8形式で記述されたものとする。

という指定を設けたが、読み込むファイルの形式が異なる場合には、一工夫必要だ。

例えば、SJIS形式のファイルの読み書きを行うのであれば、以下のような処理となる。

public class Main {
    public static void main(String[] args) {
        try{
①	      InputStreamReader reader = new InputStreamReader(new FileInputStream(args[0]),"SJIS");
              BufferedReader buff = new BufferedReader(reader);
②	      OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(args[1]),"SJIS");
              String text;
              while ((text = buff.readLine()) != null) {
                  System.out.println(text);
                  writer.write(text);
                  writer.write("\r\n");
              }
              reader.close();
              writer.close();
        }catch(FileNotFoundException fnoe){
                fnoe.printStackTrace();
        }catch(IOException ioe){
                ioe.printStackTrace();
        }
    }
}

  1. FileReaderでは無くInputStreamReaderを使用するが、その際にファイルの保存形式の文字コードを指定する。
  2. FileWriterでは無くOutputStreamWriterを使用するが、その際にファイルの保存形式の文字コードを指定する。

この記述にすることで、SJIS形式のファイルの読み書きが可能となる。

Javaでは、様々な環境で実行されるプログラムに対応するため、基本はUTF-8でテキストデータを扱うものとし、必要に応じて文字コードの変換を行うことが出来るように文字コードの変換機能が用意されている。

ファイルの読み書きに限らず、文字化けが発生した際には、どの文字コードで作成されたデータなのか、どの文字コードで出力しているのかを整理しよう。

 

 

練習問題2 解答/解説

解答例

public class Main {
    public static void main(String[] args) {
        try {
①	    FileInputStream fis = new FileInputStream(args[0]);
②          FileOutputStream fos = new FileOutputStream(args[1]);
            int c = 0 ;
            while((c = fis.read()) != -1){
                fos.write(c);
            }
③	    fis.close();
            fos.close();
        }catch(FileNotFoundException fnoe){
            fnoe.printStackTrace();
        }catch(IOException ioe){
            ioe.printStackTrace();
        }
    }
}

 

  1. ファイルの読み込みには、FileInputStreamを利用する。
  2. ファイルの出力には、FileOutputStreamを利用する
  3. 入力ファイル、出力ファイル共に、最後にclose()を忘れてはならない。

バイナリファイルの取り扱いに関する問題だ。

恐らく、ファイルというとテキスト形式のファイルを思い浮かべる人も多いかも知れないが、コンピューター上で扱うファイルの多くは、人が目で見て読み取ることのできない、バイナリ形式のファイルだ。

テキスト形式のファイルというのは、そこに記載されているデータを人が読んで理解できるように表現する必要があるため、文字コードの変換などが必要だが、バイナリ形式の場合は、そもそも特定のプログラムでなければ読み取ることは出来ないので、プログラム中での扱いはあまり複雑なものにはならない。

しかし、画像ファイルやワープロ、表計算ソフトのファイルを読み取ってデータとして扱ったり、編集する場合には、そのファイル形式に応じたプログラム処理が必要となってくる。

その場合、処理も複雑になるが、そもそもファイルの構造は細かく把握しなければならないため、プログラムの難易度は大きく跳ね上がる事を覚悟しよう。

 

練習問題3 解答/解説

解答例

public class Main {
    public static void main(String[] args) {
        try{
            InputStreamReader reader = new InputStreamReader(new FileInputStream(args[0]));
            BufferedReader buff = new BufferedReader(reader);

            String text ;
①	        List<String> list = new ArrayList<String>();

            while ((text = buff.readLine()) != null) {
②	             list.add(text);
            }
            reader.close();

            for(String line : list){
                System.out.println(line);
            }
            for(int index = list.size() - 1 ; -1 < index ; index --){
                String line = list.get(index);
                System.out.println(line);
            }
③	        int index = list.indexOf("あいうえお");
            if(index != -1){
                System.out.println((index + 1) + "行目");
            }

        }catch(FileNotFoundException fnoe){
            fnoe.printStackTrace();
        }catch(IOException ioe){
            ioe.printStackTrace();
        }
    }
}

ファイル読み込み処理については、問題1と同様なので、説明を割愛する。

  1. Listはインターフェース定義のため、実体を生成する際にはArrayListを使用する。ArrayListは、いかなる型のオブジェクトも格納可能だが、型を明示する事で記述ミスを減らす。
  2. add()メソッドを使用して、要素を追加する。
  3. get()メソッドを使用して、指定の要素番号(0から数える)の要素を取得する。
  4. indexOf()メソッドを使用して、要素の先頭から最初に見つかった同一要素の要素番号を取得する。

Listの扱い方を確認する問題だ。

まず、java.util.Listはインターフェースとして定義されているため、実体を生成する事は出来ない。

標準で提供されているクラスでも、いくつかのクラスがListインターフェースを実装しているが、最も使い勝手がよく利用頻度が高いのがjava.util.ArrayListであろう。

ここでは、Listの中身に文字列を入れることがあらかじめはっきりしているため、宣言時には

List<String> list = new ArrayList<String>();

と、「<String>」と明記する事で、コンパイルの時点で誤った要素の追加を行う記述を発見できるようにしている。

単に

List list = new ArrayList ();

と記述しても、プログラムは問題なく動作するのだが、処理が複雑なプログラムになると、Listにどのような型のオブジェクトが入っているのかわからなくなり、プログラムの誤動作につながるような誤った記述を見逃してしまう可能性が高くなる。

このような型(<String>のような記述)の事を総称型と呼び、Javaでは様々なクラスで用いられている。

Eclipseなどの開発ツールを利用すると、型の明示が無い場合に警告表示されたりするが、余程のことが無ければ総称型の記述を怠らず、安全性の高いプログラムの作成を心掛けてもらいたい。

練習問題4 解答/解説

解答例

public class Main {
    public static void main(String[] args) {
        try{
            InputStreamReader reader = new InputStreamReader(new FileInputStream(args[0]));
            BufferedReader buff = new BufferedReader(reader);

            String text ;
①	    Map<String,String> map = new HashMap<String,String>();

            while ((text = buff.readLine()) != null) {
                String[] value = text.split(" ");
②	        map.put(value[0], value[1]);
            }
            reader.close();

③	   for(Map.Entry<String, String> e : map.entrySet()) {
                System.out.println(e.getKey() + " : " + e.getValue());
            }
            String place = map.get("TOKYO");
            if(place != null){
                System.out.println("TOKYO=>" + place);
            }

        }catch(FileNotFoundException fnoe){
            fnoe.printStackTrace();
        }catch(IOException ioe){
            ioe.printStackTrace();
        }
    }
}

ファイル読み込み処理については、問題1と同様なので、説明を割愛する。

  • Mapはインターフェース定義のため、実体を生成する際にはHashMapを使用する。HashMapは、いかなる型のオブジェクトもキーおよび値として格納可能だが、型を明示する事で記述ミスを減らす。
  • put()メソッドを使用して、要素を追加する。
  • 要素のすべてを取り出す際には、entrySet()メソッドを使用し、キーと値のペアであるEntryを取得する。
  • get()メソッドを使用して、指定のキーの要素を取得する。

ここではMapの使い方を確認する問題だ。

まず注目してもらいたいのは、Mapに値を格納した際の順番に関する性質だ。

実際に解答例を実行してみるとわかるが、登録された順番に取得できるとは限らないことに注意してもらいたい。

これは、Mapインターフェースの実装であるHashMapの性質で、あくまでもキーと値のペアを格納する為のオブジェクトであり、その順番は保持されないという事だ。

もし、順序を保持したいのであれば、HashMapの代わりに、LinkedHashMapを使えば良い。使い方もHashMapとほぼ同様なので、解答例のHashMapの部分をLinkedHashMapに代えて実行してみると良いだろう。

問題5 解答/解説

解答例

public class Main {
    public static void main(String[] args) {
        try{
            InputStreamReader reader = new InputStreamReader(new FileInputStream(args[0]));
            BufferedReader buff = new BufferedReader(reader);

            String text ;
①	    Map<String,String> map = new TreeMap<String,String>();

            while ((text = buff.readLine()) != null) {
                String[] value = text.split(" ");
            map.put(value[0], value[1]);
            }
            reader.close();

        for(Map.Entry<String, String> e : map.entrySet()) {
                System.out.println(e.getKey() + " : " + e.getValue());
            }
           
        }catch(FileNotFoundException fnoe){
            fnoe.printStackTrace();
        }catch(IOException ioe){
            ioe.printStackTrace();
        }
    }
}

お気づきかもしれないが、これは問題4の「HashMap」を「TreeMap」に記述を変更しただけで、他の処理に変更は無い。

TreeMapは、Mapインターフェースの実装だが、HashMapやLinkedHashMapとは異なり、キーを一定のルールで並び替えを行って保持する性質を持っている。

この例では、TreeMapの生成時に引数を何も指定していないため、標準の順序(=アルファベット辞書順)となっているが、引数次第で特定の順序を指定する事が出来る。

解答例2

public class Main {
    public static void main(String[] args) {
        try{
            InputStreamReader reader = new InputStreamReader(new FileInputStream(args[0]));
            BufferedReader buff = new BufferedReader(reader);

            String text ;
        Map<String,String> map = new TreeMap<String,String>(new NameComparator());

            while ((text = buff.readLine()) != null) {
                String[] value = text.split(" ");
            map.put(value[0], value[1]);
            }
            reader.close();

        for(Map.Entry<String, String> e : map.entrySet()) {
                System.out.println(e.getKey() + " : " + e.getValue());
            }
           
        }catch(FileNotFoundException fnoe){
            fnoe.printStackTrace();
        }catch(IOException ioe){
            ioe.printStackTrace();
        }
    }
}
class NameComparator implements Comparator<String>{

    @Override
    public int compare(String o1, String o2) {
        if(o1.length() > o2.length()){
            return 1 ;
        }
        if(o1.length() < o2.length()){
            return -1 ;
        }
        return o1.compareTo(o2);
    }

}

先ほどの解答とは異なり、TreeMapの生成時にComparatorを指定しており、そのComparatorの実装がNameComparatorだ。

Comparatorは、2つのオブジェクトの順序付けを処理するもので、

  • 最初の引数が 2 番目の引数より小さい場合は負の整数
  • 両方が等しい場合は 0
  • 最初の引数が 2 番目の引数より大きい場合は正の整数

を返せばよい。

TreeMap内部で、順序付けによるソート処理が効率よく行われて保持される。

プログラミングにおける並び替えのアルゴリズムは数多くあるが、それらをJavaで個別に実装する必要は無い。

TreeMapやArraysクラスなど、並び替えを行う為のクラスがすでに用意されており、プログラム作成者はComparatorを定義して、比較ルールを明示するだけで良いのだ。

まとめ

このページでは、Javaの練習問題をまとめてみた。特に知識が問われる問題を中心にまとめている。

ぜひトライしてみて、Javaに対する理解を確認してみていただきたい。

]]>
https://eng-entrance.com/java-question-knowledge/feed 2
【初心者向けトレーニング】Javaの演習問題:構文編 https://eng-entrance.com/java-question-grammar https://eng-entrance.com/java-question-grammar#respond Sat, 29 Oct 2016 01:19:46 +0000 http://eng-entrance.com/?p=3185 このページではJavaの演習問題をいくつか用意した。

Java特有の文法の知識があれば解けるような演習問題たちだ。Javaを勉強中の方はチャレンジしてみてほしい。

アルゴリズムを考えて解く演習問題は下記に用意している。こちらも参考にしてほしい。

Javaの演習問題:文法編

それでは早速演習問題に入っていこう。

解答方法

入力値は問題で定められた規則に従い、正しく入力されるものとし、入力ミスなどは考慮しなくてよい。

問題1(起動パラメータ)

起動パラメータに指定された引数をすべて表示するプログラムを作成しなさい

  • 最初に、引数の個数を出力する。
  • 引数は一つ以上指定されるものとする。

実行例:

java Man ABC あいうえお 12345

出力例:

引数は3個指定されています。
1番目の引数は「ABC」です。
2番目の引数は「あいうえお」です。
3番目の引数は「12345」です。

問題2(例外)

プログラム中で、以下の例外を発生させる処理を記述し、その例外の詳細情報は出力しなさい。

  • Null参照によるNullPointerException
  • 配列の範囲外参照によるArrayIndexOutOfBoundsException
  • 互換性の無いオブジェクト型の変換によるClassCastException

ただし、プログラムは例外による異常終了はせず、正常に終了すること。

出力例:

java.lang.NullPointerException
    at Main.main(Main.java:8)
java.lang.ArrayIndexOutOfBoundsException: 0
    at Main.main(Main.java:14)
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
    at Main.main(Main.java:21)

 

問題3(static、ジェネリクス、アノテーション)

起動パラメータに指定された引数配列に対し、

  • List型の変数に格納する
  • List型の変数に格納された値を表示する

を行うプログラムを作成しなさい。

実行例:

java Man ABC あいうえお 12345

出力例:

ABC
あいうえお
12345

 


いかがだっただろうか?

以下は、演習問題の回答になるので、解き終わってから確認してほしい。

 

問題1 解答/解説

解答例

public class Main {
    public static void main(String[] args) throws Exception {
        System.out.println("引数は" + args.length + "個指定されています。");
        for(int idx = 0 ; idx < args.length; idx ++){
            System.out.println((idx + 1 ) + "番目の引数は「" + args[idx] + "」です。");
        }
    }
}

入力パラメータを数値に変換する。文字列を数値に変換する際、文字列が「-N」の形式の場合は、負の値として変換される。

まずは、javaプログラムを実行する際の基本的な問題だ。

コマンドラインからjavaプログラムを起動する事は、実はあまり多くは無いのだが、基礎知識として押さえておこう。

ポイントとしては

  • main()メソッドの引数String[]には、コマンドライン引数が渡される。
  • 引数String[]には、プログラムに渡されるコマンドライン引数のみが格納され、コマンドラインオプションや実行クラス名は含まれない。

というところだ。

この2つ目の「コマンドラインオプションや実行クラス名は含まれない」というところに「なんでわざわざそんなことを」と思われたかも知れない。

実は、プログラム言語によっては、起動引数の1番目に実行プログラム名が格納され、引数は2番目以降となるものがある。C言語などがそうだ。

引数だけが渡されることが当たり前ではないので注意してもらいたい。

問題2 解答/解説

解答例

public class Main {
    public static void main(String[] args) throws Exception {
        try{
            String name = null ;
	        name = name.substring(0);//①
        }catch(Exception ex){
            ex.printStackTrace();
        }
        try{
            String values[] = new String[2];
	        values[2] = "value";//②
        }catch(Exception ex){
            ex.printStackTrace();
        }

        try{
            Object value = "value";
	        Integer number = (Integer)value;//③
        }catch(Exception ex){
            ex.printStackTrace();
        }
    }
}

javaでは、try/catchという構文を利用して、例外の捕捉(ハンドリング)を行う。

まず、プログラム中に例外(Exception)が発生した際に、適切な処理継続(安全な終了処理を含む)のために、「例外の捕捉」を行う必要がある。

NullPointerExceptionの発生要因は、Nullオブジェクトに対するメソッドの呼び出しだ。

①では、変数nameは宣言後にnullで初期化され、その後実体の代入処理が無くsubstringメソッドが呼び出されている為にNullPointerExceptionが発生する。

この問題を回避するためには、宣言した変数がどのタイミングで値(オブジェクト)が代入されるのかを意識しながら全体の処理を記述する必要がある。

 

ArrayIndexOutOfBoundsExceptionの発生要因は、配列の範囲外の要素へのアクセスだ。

例えば、10個しか要素を用意していない配列なのに、11番目(添え字は10)の要素に代入したり参照したりすると発生する。

②では添え字「2」の要素(1番目の要素)に対して代入を行っているが、配列valuesを宣言した際に定義した要素数は「2」であり、添え字の最大番号は「1」(「0」から始まって2つ目は「1」)であり、添え字「2」に該当する要素は存在しないため、ArrayIndexOutOfBoundsExceptionが発生する。

 

この問題を回避するためには、配列の最大数(添え字の最大番号)は幾つになるのかを意識しながら全体の処理を記述する必要がある。

また、配列の場合はlengthプロパティを参集するなどして、要素数の変化に耐えうる処理を記述するよう心掛けると良いだろう。

 

ClassCastExceptionの発生要因は、互換性のオブジェクトの型変換(キャスト)だ。

③では、Object型として宣言された変数に、String型のオブジェクトが代入されたものを、互換性の無いInteger型変換しようとしているためにClassCastExceptionが発生する。

Object型はその名のとおりオブジェクト型の変数の値であればどのようなものでも代入可能で、String型のオブジェクトを代入する事も、Integer型にキャストする事も可能なため、コンパイルエラーにならないが、実行時に実際に格納されている型がString型のものをInteger型に変換するという事は、互換性の無い型同士での変換のため、Exceptionが発生してしまう。

 

この問題を回避するためには、そのオブジェクトが実態としてどんな型のオブジェクトを保持しているのかを意識しながら全体の処理を記述する必要がある。

また、ArrayListやHashMapのようなObject型を格納するオブジェクトには、ジェネリクスという型指定の考え方がある。

 List<String> array = new ArrayList<String> ;

というように、変数の宣言時に扱う型を限定してしまうのだ。

これにより、実行時にClassCastExceptionが発生する可能性は低下するだろう。

 

ここで紹介した3つのExceptionは、どれもよく見かけるものなのだが、発生原因の多くは考慮不足によるものだ。

また、3つのExceptionはRuntimeExceptionの派生で、本来は発生しないものである。

Exceptionには大きく分けて

  • 起こりうることが予想できるException
  • 起こりうることが予想できず、多くの場合はプログラム中の問題である

の2つに分類され、後者はRuntimeExceptionを指している。

 

前者は、ファイルアクセス処理において、プログラムの実行時にファイルが見つからずに「IOException」が発生するケースなどが良い例だ。

プログラムとしては、処理を行うべきファイルが必ず存在する前提でプログラムを実行するのだが、プログラム外の問題でもあるので、「起こりうることが予想できるException」としてtry/catch文を利用して「Exceptionが発生したときの対処方法」を事前に明確にしておくべきだろう。

 

RuntimeExceptionでは無い例外の場合、必ずハンドリング処理を実装しなければならないようにjavaの言語仕様として定められている。

これに対し、RuntimeExceptionはあくまでも実行時の例外であり、事前に予測のできるものではない。しかしながら、プログラムの実行パターンを不足なく確認できれば、RuntimeExceptionの発生を抑えることは可能だ。

プログラム経験の浅いうちはRuntimeExceptionが多発するが、習熟度が上がるにつれてあまり見かけなくなるものでもある。

2つのExceptionの違いを最初から理解するのは難しいかも知れないが、少しずつ慣れていこう。

 

問題3 解答/解説

解答例

public class Main {
    public static void main(String[] args) throws Exception {
        List<String> params = Arrays.asList(args);
        for(String param : params){
            System.out.println(param);
        }
   }
}

この問題は、何のひねりもない陳腐な問題と思われた方もいるだろう。

しかし、Javaにおけるいくつかの重要な要素を含んでいるので、解説をよく読んで理解を含めて欲しい。

static

プログラム中の以下の記述に注目してもらいたい。

List<String> params = Arrays.asList(args);

これはString配列をList型に変換する処理を行う、Java標準の機能を使用したものだ。

ここでは、「Arrays」というクラスの「asList()」というメソッドを呼び出しているが、Arraysは変数では無く、クラス名を指している。

このように、クラスの実体を生成する事無く、そのまま使用できるメソッドの事を「staticメソッド」と呼ぶ。

staticメソッドはJava標準機能でもいくつか用意されており、いずれも多くのプログラムで利用されるような汎用的な機能だ。

ジェネリクス

本来、リスト型の変数には、StringのみならずIntegerやDateなど、どのような型のオブジェクトも格納可能だ。

しかし、取り出す側のプログラムを記述する立場から見れば、どんな型のオブジェクトが入っているのかが分からないという曖昧さが生まれ、取り出した値の取り扱いに困ってしまう。

これを解決したのが「ジェネリクス」という考え方だ。

Listのように、どんなクラスのオブジェクトでも扱えるクラスであっても、変数の宣言時にその用途を限定することで、プログラムの曖昧さを抑制するものだ。

ジェネリクスの考え方を適用せずにこのプログラムを記述すると、以下のようになる。

public class Main {
    public static void main(String[] args) throws Exception {
        List params = Arrays.asList(args);
        for(int idx = 0 ; idx < params.size(); idx ++){
            String param = (String)params.get(idx);
            System.out.println(param);
        }
    }
}

しかし、EclipseなどのIDEを使用してコンパイルを行うと、警告が出ていることが分かるだろう。実際にこのプログラムを作成し、実行すると正しく動作する。

これはList変数の宣言時に型を明示していないために出されている警告だ。

また、最初の解答例のように、拡張for記述を使用することも出来ず、コンパイルエラーとなる。

for(String param : params){
            System.out.println(param);
        }

これも、配列要素のオブジェクトの型が明示されていないからだ。

あえて拡張for記述を用いるとすれば

for(Object param : params){
            System.out.println(param);
        }

この場合、paramをStringとして使用するのであればと記述するしかない。

(String)param

と、キャスト(強制的な型変換)を行う必要がある。

アノテーション

上記のジェネリクスのケースで、どうしても警告が出て気になる場合には、警告の抑制を行うことができる。

public class Main {
    public static void main(String[] args) throws Exception {
        @SuppressWarnings("rawtypes")
        List params = Arrays.asList(args);
        for(int idx = 0 ; idx < params.size(); idx ++){
            String param = (String)params.get(idx);
            System.out.println(param);
        }
    }
}

このように、

@SuppressWarnings("rawtypes")

という記述を追加するだけで、型定義の曖昧さに関する警告は抑制される。このように「@」で始まる記述を「アノテーション」と呼ぶ。

アノテーションは「注釈」と呼ばれ、様々なものがある。単にコメント的な意味を表すものや、使ってほしくないメソッドに対する警告的なものなど様々だ。

また、最新のJavaフレームワークであるJava EEでは、サーバーサイドプログラムの実行定義に関する命令を、アノテーションを使用して記述する。

このように、様々な目的でアノテーションが使用されているが、使う場面は熟慮すべきである。

例えば、先の

@SuppressWarnings("rawtypes")

のような記述は、極力利用すべきでは無い。

なぜなら、ジェネリクスによる型の明示は、プログラムの品質を高めるために考案されたものであるのに対し、@SuppressWarningsを記述することで、その警告を隠ぺいしてしまうことになるからだ。

 

あえて利用する場面としては、旧バージョンのJava(1.4以前)で作成した共通処理のソースコードを、そのまま利用して新バージョンのJava向けの開発に利用するようなケースだろうか。

1.4以前のJavaでは、ジェネリクスというものが存在しなかったため、型の明示はせずにあいまいなまま使用していた。

そのプログラムを、そのまま改変せずに新バージョンで使用すると、至る所で警告が発せられることになるだろう。

かといって、警告個所をジェネリクス記述に訂正する事で、不要なミスを呼び起こす危険も考えられる。

そんな場合であれば@SuppressWarningsを用いて、警告を隠ぺいしても良いだろう。

 

いずれにしても、アノテーションは適した場面で活用すれば、プログラム作成の簡素化や品質の向上につながるものだ。

是非とも活用してもらいたい。

まとめ

このページではJava特有の文法的な問題についてまとめてきた。

ぜひ解答だけ見ずに、実際にトライをしてみて、スキルを上げていただければと思う。

]]>
https://eng-entrance.com/java-question-grammar/feed 0
【初心者向けトレーニング】Javaの演習問題:アルゴリズム編 https://eng-entrance.com/java-question-algorithm https://eng-entrance.com/java-question-algorithm#comments Tue, 13 Sep 2016 23:21:03 +0000 http://eng-entrance.com/?p=2627 このページではJavaの演習問題をいくつか用意した。

特にアルゴリズムを考える問題に絞ってこのページではまとめている。Javaを勉強中の方はチャレンジしてみてほしい。

演習問題の前提

本編では、プログラミング演習に関する焦点を、アルゴリズム部分に絞るため、プログラムのひな型として、下記を使用するものとする。

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        // 入力パラメータを読み込む
        String input = br.readLine();
String[] param = input.split(" ");//入力値を空白で分解する
        /**
         * ここに回答を記述する
        **/
    }
}

  • 入力値は空白で区切られ、変数paramに代入される。
  • 入力値は問題で定められた規則に従い、正しく入力されるものとし、入力ミスなどは考慮しなくてよい。

Javaアルゴリズム演習問題

それではJavaの演習問題を解いて行こう。回答は最後に載っているが、すぐに見ないでトライしてみていただきたい。

問題1(配列)

入力された2つの数の和を出力するプログラムを作成しなさい。

  • 入力する数は、共に整数とする。
  • 負の数も含む。負の数の場合は、数値の直前に空白を挟まずに「-」(マイナス)を付加して入力するものとし、数値の範囲は-10000~10000とする。

入力例:

  5 4

出力例:

  9

問題2(分岐/多重分岐)

入力された四則演算の結果を出力するプログラムを作成しなさい。下記が条件だ。

  • 入力する数は、すべて整数とする。
  • 演算記号は「+」(和) 「-」(差) 「*」(積) 「/」(商)を使用する。
  • 四則演算の式は、1回の演算とし、2回以上の演算は行わない。(正しい:23 * 58 / 誤り: 23 + 58 + 7)
  • 商を求める際は、余りを「 … N」と表示する
  • 「0」(ゼロ)で割る式は入力しない
  • 負の数も含む。負の数の場合は、数値の直前に空白を挟まずに「-」(マイナス)を付加して入力するものとし、数値の範囲は-10000~10000とする。

入力例

  23 * 58

出力例

  1334

問題3(繰り返し)

入力された加算/減算演算の結果を出力するプログラムを作成しなさい。

  • 入力する数は、すべて整数とする。
  • 演算記号は「+」(和) 「-」(差)を使用する。
  • 演算の式は、最低1回の演算を行うものとし、複数回の演算を行う場合もある。(正しい:      23 + 58 + 7)
  • 負の数も含む。負の数の場合は、数値の直前に空白を挟まずに「-」(マイナス)を付加して入力するものとし、数値の範囲は-10000~10000とする。
  • 演算の途中経過における取りうる値の反は-10000~10000とする。

入力例

  23 + 58 + 7

出力例

  88

問題4(再帰)

入力された加算/減算演算の結果を出力するプログラムを作成しなさい。

  • 入力する数は、すべて整数とする。
  • 演算記号は「+」(和) 「-」(差)を使用する。
  • 演算の式は、最低1回の演算を行うものとし、複数回の演算を行う場合もある。
  • 四則演算の記法に乗っ取り、先に行うべき演算は「(」および「)」でくくるものとし、入れ子構造も可とする。【正しい:  23 + 58 + 7 – ( 10 + 3 ) – ( 3 – ( 5 + 4  – ( 8 – 2 ) ) )】
  • 負の数も含む。負の数の場合は、数値の直前に空白を挟まずに「-」(マイナス)を付加して入力するものとし、数値の範囲は-10000~10000とする。
  • 演算の途中経過における取りうる値の反は-10000~10000とする。

入力例

  23 + 58 + 7 - ( 10 + 3 ) - ( 3 - 2 - ( 5 + 4 - ( 8 - 2 ) + 8 ) )

出力例

  85

 

 

 

 

演習問題解答

ここから下は演習問題の解答だ。トライしてみてから、確認しよう。

問題1 解答/解説

解答例

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        // 入力パラメータを読み込む
        String input = br.readLine();
        String[] param = input.split(" ");//入力値を空白で分解する
	int num1 = Integer.parseInt(param[0]);//①
    int num2 = Integer.parseInt(param[1]);
  System.out.println(num1 + num2);
    }
}

①:入力パラメータを数値に変換します。文字列を数値に変換する際、文字列が「-N」の形式の場合は、負の値として変換される。

まずは、入力された数値を加算するだけのシンプルな問題だ。

注意すべきポイントは

・配列の添え字は「0」(ゼロ)から始まる。

というところだ。

問題2 解答/解説

解答例

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        // 入力パラメータを読み込む
        String input = br.readLine();
        String[] param = input.split(" ");//入力値を空白で分解する
        int num1 = Integer.parseInt(param[0]);
        String type = param[1] ;
        int num2 = Integer.parseInt(param[2]);
        if(type.equals("+")){ //①
            System.out.println(num1 + num2);
        }else if(type.equals("-")){
            System.out.println(num1 - num2);
        }else if(type.equals("*")){
            System.out.println(num1 * num2);
        }else if(type.equals("/")){
            System.out.println(num1 / num2 + " ... " +  num1 % num2);//②
        }
    }
}

  • ①:文字列の比較は、「==」では無く、Stringクラスのequals()メソッドを使用する。
  • ②:割り算の余りを求める際には、「%」演算子を使用する。

条件判定を行い、その結果に応じて結果を出力すればよい。ポイントとしては、

if/elseを使用して

if(…){
            
}else if(…){

}

のように、多重分岐のパターンを網羅する辺りだろう。

また、開発環境がJDK1.7以降であれば、switch文にString型を使用する事が出来るので、下記のように記述しても良い。

switch(type){
            case  "+" :
                System.out.println(num1 + num2);
                break;
            case  "-" :
                System.out.println(num1 - num2);
                break;
            case  "*" :
                System.out.println(num1 * num2);
                break;
            case  "/" :
                System.out.println(num1 / num2 + " ... " +  num1 % num2);
                break;
        }

switch文は、if/else ifに比べて、多重分岐の記述が容易であるというメリットがあるが、使用頻度が低く、記述ミスを生みやすいので、十分注意してもらいたい。

問題3 解答/解説

解答例

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        // 入力パラメータを読み込む
        String input = br.readLine();
        String[] param = input.split(" ");//入力値を空白で分解する
        String type = null ;
        Integer sum = 0 ;
        for(int idx = 0 ; idx < param.length; idx ++){//①
        if(param[idx].equals("+") || param[idx].equals("-")){//②
                type = param[idx] ;
        }else{
                Integer num = Integer.parseInt(param[idx]);//③
                if(type != null){//④
                    if(type.equals("+")){
                        sum = sum + num;
                    }else if(type.equals("-")){
                        sum = sum - num;
                    }
                }else{
                  sum = num ;//⑤
                }
            }
        }
        System.out.println(sum);
    }
}

  • ①:入力パラメータを順に処理する。
  • ②:入力パラメータが演算記号の場合、演算記号を変数に格納する。
  • ③:入力パラメータが演算記号でない場合、数値に変換する。
  • ④:上記③で演算記号が格納されている場合、これまでの合計値と、その演算記号に応じた演算を行い、合計値として設定する。
  • ⑤:上記③で演算記号が格納されていない場合(=先頭の数字の場合)、その値を合計値として設定する。

入力する数値の個数が決まっていないため、繰り返し処理を必要とする。(①の部分)

基本的な考え方として、処理の対象となる文字列が演算記号なのか(②)、そうでない数字(③)なのかで分岐し、数字の場合は指定された演算記号に応じた演算を行う(④)。

ただし、先頭の数字の場合は、まだ演算記号が登場していない。(⑤の部分)

従って演算は行わずに、演算結果として合計値を保存する変数に代入する。

繰り返し処理の場合には、繰り返しの全ケースで同じことが出来るとは限らず、例外的なケースを漏らさず見つけることが重要だ。

問題4 解答/解説

解答例

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        // 入力パラメータを読み込む
        String input = br.readLine();
        String[] param = input.split(" ");//入力値を空白で分解する
        int sum = calc(param,0,param.length - 1) ;
        System.out.println(sum);
    }

    public static int calc(String[] param, int startIdx, int endIdx){
        int sum = 0 ;
        String type = null ;
        for(int idx = startIdx ; idx < endIdx + 1 ; idx ++){
            if(param[idx].equals("+") || param[idx].equals("-")){
                type = param[idx] ;
            }else if(param[idx].equals("(")){
                int endOfFormula = checkEndOfFormula(param,idx) ;//③
                if(type != null){
                    if(type.equals("+")){
                       sum = sum + calc(param, idx + 1 , endOfFormula - 1);//①
                    }else if(type.equals("-")){
                       sum = sum - calc(param, idx + 1 , endOfFormula - 1);//②
                    }
                }else{
                    sum = calc(param, idx + 1 , endOfFormula - 1) ;
                }
                idx = endOfFormula ;
            }else{
                int num = Integer.parseInt(param[idx]);
                if(type != null){
                    if(type.equals("+")){
                        sum = sum + num;
                    }else if(type.equals("-")){
                        sum = sum - num;
                    }
                }else{
                    sum = num ;
                }
            }
        }
        return sum ;
    }

    //かっこ「(」の終わりを見つける
    public static int checkEndOfFormula(String[] param, int startIdx){
        int end = startIdx + 1;
        int startCount = 0 ;
        for(int idx = startIdx + 1 ; idx < param.length; idx ++){
            end = idx ;
            if(param[idx].equals("(")){
                startCount ++ ;//④
            }else if(param[idx].equals(")")){
                if(startCount == 0){
                    break ;
                }else{
                    startCount -- ;//⑤
                }
            }
        }
        return end ;
    }
}

  • ①、② calc()メソッドを、再帰的に呼び出す。

この問題の場合はまず考え方を整理する必要がある。

例えば、「7 – ( 5 – 2 )」という入力を考えたとき、左から順に計算をしていくと、7から引くべき数は( 5 – 2 )となるため、最初に 5 – 2の計算をしなければならない。

もちろん、( ) を入れ子にすることが出来るので、さらに小分けにする可能性も出てくる。

そこで、計算式を解析する処理をcalc()メソッドに分け、その中で「(」を見つけた場合には、その『「(」の終わりまでの部分を別で計算する為にcalc()メソッドを呼ぶ』という再帰処理を作成する事にする。

calc()メソッドは、パラメータ配列と、計算をする先頭と終端の添え字を渡すことで結果を返してくれるものだ。

例えば、入力が先の【  7 – ( 5 – 2 ) 】だとすると、パラメータ配列は以下のようになる。

添え字

0

1

2

3

4

5

6

パラメータ配列

7

-

(

5

-

2

)

この中の「 5 - 2」の部分を計算する場合、計算をする先頭の添え字「3」と終端の添え字の「5」を渡すことで、5 - 2を計算してくれる。(①、②の部分)

calc()メソッドは、その中で再帰的にcalc()メソッドを呼び出しているため、「(」を見つけた場合は、その「(」の終端を見つけ、calc()メソッドにその間の部分を計算するように呼び出せばよい。

「(」の終端を見つけるためにはcheckEndOfFormula()メソッドを利用する。(③の部分)

「(」の終端とは、単に「)」を見つけるのではなく、入れ子構造となている可能性を考慮する。

例えば、

   3 – ( 5 – ( 2 + 1 ) )

のような式を考える。

添え字

0

1

2

3

4

5

6

7

8

9

10

パラメータ配列

3

-

(

5

-

(

2

+

1

)

)

この場合、添え字「2」の「(」の終端は、添え字「9」では無く「10」の「)」のほうだ。

しかし、見た目では分かるのだが、プログラムとするためには途中の( )をどう扱うかがポイントだ。

模範解答では、④の部分で途中に現れた「(」の数をカウントし、⑤では「」」が現れると減算している。

これは、途中に「(」や「)」が何個あろうと、必ず同じ数だけあるため、( )が終わっていれば、カウントは「0」(ゼロ)になるという発想からきている。

このように、( )の位置を判定するcheckEndOfFormula()メソッドと、計算を行うcalc()メソッドを利用する事で、計算結果を算出する事が出来る。

まとめ

このページではJavaでアルゴリズムを考えるような問題を用意してみた。初心者の方向けなので、そこまで難しくなかったかもしれない。後半は徐々に難易度が上がっていくだろう。

ぜひ理解度やプログラミングの応用力をチェックするのに役立ててほしい。

]]>
https://eng-entrance.com/java-question-algorithm/feed 2