対話型プログラム

今回の内容

今回はコンピューターと人間が対話しながら処理を進める「対話型プログラム」というものについて紹介します。これまで紹介したプログラムのほとんどは、プログラムの前半で一気に入力を行って、それらを処理し、最後に結果を表示するというものでした。それに対して対話型プログラムでは、出力された結果を人間が判断して、さらなる入力を行うというような対話のような形式で処理が進められます。

図 :対話型プログラム

みなさんが利用する多くのソフトウェアは対話型プログラムとして作成されていることがほとんどです。たとえばWebブラウザ を開いてどこかのサイトを訪問しても、そのままでは画面に収まる範囲のページが表示されて終わりです。そこから先は画面をスクロールして別の部分を表示したり、ハイパーリンクをクリックして別のページへ移動したり、というのを繰り返しながら様々なWebサイトを閲覧していると思います。このように、少し入力したら何かが出力され、それを見てさらに次の入力をするというように、人間とコンピューターが交互に「入力→処理→出力」を繰り返して行くのがこの「対話型プログラム」の特徴です。

今回の内容で、人間からのフィードバックを受けながら複雑な処理を行うようなプログラムを作成できるようになります。また複雑でなくても、ゲームなどの主に人間とコンピューターの対話を前提としたプログラムなども「対話型プログラム」として作成できます。

今回の作業

今回の例題、練習問題、課題は以下のパッケージに作成してください。

パッケージの名前
j1.lesson09

クラスの作成方法については、「クラスを作成する」を参照してください。

対話型プログラム

無限ループ

対話型プログラムの第1歩として、「入力→処理→出力」を無限に繰り返すプログラムについて紹介します。

リスト: LoopSum(無限ループ)
package j1.lesson09;

import javax.swing.JOptionPane;

public class LoopSum {

    public static void main(String[] args) {
        new LoopSum().start();
    }

    void start() {
        int total = 0;
        while (true) {
            int value = getInput();
            total = total + value;
            JOptionPane.showMessageDialog(null,
                value + "が加算されました。これまでの合計は" + total);
        }
    }

    int getInput() {
        String input = JOptionPane.showInputDialog("整数を入力してください");
        int value = Integer.parseInt(input);
        return value;
    }
}

このプログラムを実行すると「入力→処理→出力」を無限に繰り返す ため、いつまでたってもプログラムが終了しません。そこで、プログラムを強制的に停止するの手順でプログラムの実行を強制的に終了させてください。

プログラムの実行を終了させたら、プログラムの中身を見てみましょう。新しい内容として、whileから始まる文を使用しています。

リスト: while文
while (true) {
    ...
}

この文は「while文」と言って、直後のかっこの中に書いた条件が成立している間はブロックの中の命令を繰り返す、というものです。過去に紹介したfor文と似ていますが、繰り返し変数の宣言や、繰り返し変数の更新を行う部分が省略されています。

while (<繰り返す条件>) {
    <繰り返す命令>
}

今回のプログラムでは、繰り返す条件に論理型の「true (真)」というリテラルを指定しています。これは「成立する」ということを表すので、このwhile文は無限に繰り返すことになります。無限に繰り返す内容はこれまでのプログラムと同じで、整数を入力し、それを変数totalに加算して、変数totalの内容を表示しています。

リスト: 無限に繰り返すプログラム
int total = 0;
while (true) {
    int value = getInput();
    total = total + value;
    JOptionPane.showMessageDialog(null,
        value + "が加算されました。これまでの合計は" + total);
}

ただし、この一連の命令は何度も繰り返し実行されるため、変数totalの内容は繰り返しの中で累積して行きます。非常に単純ですが、対話の中で変数totalの内容を少しずつ変えて行くという対話型プログラムの基礎となるプログラムです。

図: 対話ループ

このままではプログラムと永遠に対話し続けるという状況になってしまいます。次はこれをプログラムの中で打ち切る方法を紹介します。

繰り返しの打切り

先ほどのプログラムは「入力→処理→出力」をいつまでも繰り返して終了させられなかったので、対話の中でプログラムを終了させるように変更してみます。次のプログラムは先ほど紹介したプログラムを少し変更したものです。

リスト: LoopSum(繰り返しの打切り)
package j1.lesson09;

import javax.swing.JOptionPane;

public class LoopSum {

    public static void main(String[] args) {
        new LoopSum().start();
    }

    void start() {
        int total = 0;
        while (true) {
            int value = getInput();
            if (value == 0) {
                break;
            }
            total = total + value;
            JOptionPane.showMessageDialog(null, value +
                "が加算されました。これまでの合計は" + total);
        }
        JOptionPane.showMessageDialog(null, "終了しました。これまでの合計は" + total);
    }

    int getInput() {
        String input = JOptionPane.showInputDialog("整数を入力してください(0で終了)");
        int value = Integer.parseInt(input);
        return value;
    }
}

先ほどとの違いは、0を入力すると最後のメッセージを表示して終了する点です。プログラムの中には、そのための処理として「break;」という命令を新たに追加しています。

リスト: break文
while (true) {
    int value = getInput();
    if (value == 0) {
        break;
    }
    ...
}

このbreakという命令を処理すると、現在の繰り返し を強制的に終了させられます。Javaではこれを「break文」と呼び、while文の内部でbreak文が処理されると、while文による繰り返しをその場で終了させて、whileの次の命令を処理するようになります。

このプログラムでは、while文の次に「終了しました。これまでの合計は~」と表示する命令を書いています。そのため、入力ダイアログに0と入力すると、以降は繰り返しを行わずにこのメッセージを表示してプログラムを終了します。

リスト: ループからの脱出
while (true) {
    ...
}
JOptionPane.showMessageDialog(null, "終了しました。これまでの合計は" + total);
図: 対話ループの打切り

まとめると、次のような一連の流れで「利用者と対話し、特定の値が来たら終了」という対話型のプログラムを作成できます。重要な点は、while文による無限ループで対話を行いつつ、特定の値が入力されたらbreak文で繰り返しを打ち切るという点です。繰り返しを打ち切るとwhile文の次の命令が処理されますので、次の行に終了時の処理を書くこともできます。

while (true) {
    <入力の命令>
    if (<特定の値が入力されたか?>) {
        break;
    }
    <入力を利用した処理>
}
<終了時の処理>

なお、このbreak文はこれまでに紹介したfor文の内部でも利用できます。効果はwhile文と同様で、「繰り返しを強制的に打ち切る」ということができます。「for文を使って配列の要素から特定の値を探す」といった処理などで、探し当てた後に繰り返しが必要なくなった場合などに利用できます。ただし、break文を多用するとプログラムの見通しが悪くなってしまいますので、使いどころには気をつけましょう。

練習: 繰り返しの打切り

作成するクラスの名前
LoopMessage

下記のプログラムを変更して、入力した番号に応じて5種類のメッセージを表示するプログラムを作成しなさい。入力できる番号は0から4の間で、それぞれに応じたメッセージを表示すること。また、この入力から表示は何度も繰り返しますが、-1が入力されたらそこで終了すること。

なお、あらかじめ用意してある配列の中身は自由に書き換えて下さい。

リスト: LoopMessage
package j1.lesson09;

import javax.swing.JOptionPane;

public class LoopMessage {

    public static void main(String[] args) {
        new LoopMessage().start();
    }

    void start() {
        String[] messages = new String[5];
        messages[0] = "おはよう";
        messages[1] = "こんにちは";
        messages[2] = "こんばんは";
        messages[3] = "さようなら";
        messages[4] = "おやすみなさい";
        JOptionPane.showMessageDialog(null, "プログラムを終了します");
    }

    int getInput() {
        String input = JOptionPane.showInputDialog("整数を入力してください(-1で終了)");
        int value = Integer.parseInt(input);
        return value;
    }
}

入力検査と再入力

もう1つのパターンとして、「正しい入力が行われるまで繰り返す」というプログラムを紹介します。次のプログラムは、先ほどのプログラムをさらに変更したものです。

リスト: LoopSum(入力検査)
package j1.lesson09;

import javax.swing.JOptionPane;

public class LoopSum {

    public static void main(String[] args) {
        new LoopSum().start();
    }

    void start() {
        int total = 0;
        while (true) {
            int value = getInput();
            if (value == 0) {
                break;
            }
            total = total + value;
            JOptionPane.showMessageDialog(null, value +
                "が加算されました。これまでの合計は" + total);
        }
        JOptionPane.showMessageDialog(null, "終了しました。これまでの合計は" + total);
    }

    int getInput() {
        while (true) {
            String input = JOptionPane.showInputDialog(
                "正の整数を入力してください(0で終了)");
            int value = Integer.parseInt(input);
            if (value >= 0) {
                return value;
            }
            JOptionPane.showMessageDialog(null,
                "負の値は入力できません: " + value);
        }
    }
}

今回の変更は、0未満の値を入力した際にメッセージを表示して再入力させている点です。これまでは間違った値を入力すると即座にプログラムを終了させていましたが、対話型プログラムでそれをやってしまうと、途中まで入力した結果がすべて無駄になってしまう場合があります。そのため、入力が間違っていることを伝えた上で再入力を行うという処理を加えています。

プログラムで変更した個所は、入力ダイアログを表示していたgetInputメソッドの中身だけです。全体をwhile文の無限ループで囲んでいます。

リスト: getInputメソッドの変更点
int getInput() {
    while (true) {
        String input = JOptionPane.showInputDialog("...");
        ...
    }
}

ただし、先ほどと異なり繰り返しを打ち切るためのbreak文を使っていません。代わりにメソッドの処理を終了させるreturn文を使っています。メソッドの中で行われている無限ループは、メソッドの処理そのものを終了させることで繰り返しを打ち切ることができます。

リスト: return文によるループの脱出
while (true) {
    int value = ...;
    if (value >= 0) {
        return value;
    }
    JOptionPane.showMessageDialog(null, "負の値は入力できません: " + value);
}

このreturn文で返す値は必ず0以上の値です。それ以外である0未満の値が入力された場合にはメソッドを終了せず、「負の値は入力できません: ~」というメッセージを表示しています。さらにwhile文によってこれら全体の処理が繰り返され、もう一度入力メッセージが表示されます。

図: 再入力ループ

まとめると、「正しい値が入力されるまで何度も入力させる」というプログラムを書くには、下記のように正しい値が来るまでwhile文で処理を繰り返す、といった書き方ができます。繰り返しを打ち切るにはbreak文を使ってもreturn文を使ってもかまいませんが、この項では「return文で正しい値を返す」という方法について紹介しました。なお、このwhile文全体は値を返すメソッドの中に書く必要があります。

while (true) {
    <入力の命令>
    if (<正しい値が入力されたか?>) {
        return <入力された値>;
    }
    <正しい値を入力させるためのメッセージ>
}

以上が対話型プログラムの基本的な内容です。プログラムの利用者と対話を行いながら少しずつ処理を進めるという場合には、while文で対話を繰り返しながら、条件に応じてbreak文やreturn文で対話を打ち切ります。この他にも対話型プログラムの実現方法は無数にありますが、今回の内容を基本的なパターンとして覚えておくとよいでしょう。

練習: 入力検査と再入力

作成するクラスの名前
Grading

得点を整数で入力し、その範囲によって成績を表示するプログラムを作成しなさい。得点は0点以上100点以下とし、

  • 0点以上60点未満は "D",
  • 60点以上70点未満は "C",
  • 70点以上80点未満は "B",
  • 80点以上90点未満は "A",
  • 90点以上100点以下は "A+"

とそれぞれ表示すること。

ただし、上記の範囲外(0点未満、101点以上)が入力された場合には、「範囲外の得点です」と表示した上で再度得点を入力させるようにすること。

でたらめな値

疑似乱数

対話型プログラムを紹介したついでに、「ランダム」という考え方についても少しだけ触れておきます。ランダム (random)は日本語で「でたらめの」というような意味を持っていて、その意味の通り毎回違う結果が出るような処理のことを指します。これはゲーム などのプログラムによく見られ、「10%で~」などの確率的な処理を実現するために使われます。

コンピューターででたらめな値を取り扱うには、「疑似乱数」というがよく使われます。これは計算によって疑似的にでたらめな数値 を作り出す仕組みで、Javaからも利用できます。これを使ったプログラム を見てみましょう。

リスト: RandomDice
package j1.lesson09;

import javax.swing.JOptionPane;

public class RandomDice {

    public static void main(String[] args) {
        new RandomDice().start();
    }

    void start() {
        while (true) {
            int dice = toss();
            JOptionPane.showMessageDialog(null, "サイコロの目は: " + dice);
            if(isCompleted()) {
                break;
            }
        }
        JOptionPane.showMessageDialog(null, "終了しました。");
    }

    int toss() {
        double random = Math.random() * 6;
        int dice = (int) random + 1;
        return dice;
    }

    boolean isCompleted() {
        while (true) {
            String input = JOptionPane.showInputDialog(
                "続けますか?(1)続ける (2)やめる");
            int value = Integer.parseInt(input);
            if (value == 1) {
                return false;
            }
            else if (value == 2) {
                return true;
            }
            JOptionPane.showMessageDialog(null, "1か2を入力してください: " + value);
        }
    }
}

このプログラムは疑似乱数を使ってランダムなサイコロの目をシミュレーションしています。また、全体を対話型プログラムの形式で作成していて、何度もサイコロを振れるようになっています。

まず、疑似乱数を使用している部分を見てみましょう。重要な点は「Math.random()」という個所です。

リスト: 擬似乱数の生成
int toss() {
    double random = Math.random() * 6;
    int dice = (int) random + 1;
    return dice;
}

「Math.random()」は0以上1未満の疑似乱数 を生成する命令で、これを6倍して0以上6未満の実数をrandomという変数に記憶させています。その次の「(int) random」という書き方は、randomに記憶させた実数の小数部を切り捨てて整数(int)に変換する命令です。randomに記憶させた実数は0以上6未満ですので、これで0以上5以下の整数を作り出せます。それにさらに1を足しているので、変数diceには1以上6以下という現実世界のサイコロと同じ範囲の整数を記憶させることになります。

図: 乱数の範囲を調整

次にstartメソッドの内部を見てみると、繰り返しの最初で先ほど作成したサイコロと同じ範囲の整数を受け取って、その整数を表示しています。この繰り返しは対話型プログラムのパターンを使って書いていて、isCompletedメソッドを起動してtrue (真)が返ってきたらbreak文で繰り返しを打ち切っています。

リスト: 擬似乱数の利用
while (true) {
    int dice = toss();
    JOptionPane.showMessageDialog(null, "サイコロの目は: " + dice);
    if(isCompleted()) {
        break;
    }
}

最後に、isCompletedメソッドの中身は入力の検査と再入力のパターンを使って書いています。入力した値が1または2ならメソッドを終了しますが、それ以外の値では再入力させています。また、このメソッドがtrueを返すとstartメソッドの繰り返しは終了します。

リスト: 入力検査と再入力のパターン
boolean isCompleted() {
    while (true) {
        String input = JOptionPane.showInputDialog("続けますか?...");
        int value = Integer.parseInt(input);
        if (value == 1) {
            return false;
        }
        else if (value == 2) {
            return true;
        }
        JOptionPane.showMessageDialog(null, "1か2を入力してください: " + value);
    }
}

まとめると、「Math.random()」という命令を使うと0以上1未満の実数を作成できます。その値を何倍かした後に「(int) random」のように小数を切り捨てて整数として扱うこともできます。この2つの操作をまとめて、次のように書くこともできます。

int random = (int)(Math.random() * <事象の個数>);

今回はサイコロの目が1から6までの6種類でしたので、「(int)(Math.random() * 6)」と書くことで0から5までのでたらめな値を作成できます。これに1を加算してやれば、サイコロの目と同じ1から6までのでたらめな値にすることもできます。

疑似乱数は、みなさんがインターネットで暗号化されたページを利用する際にも裏側で使われています。でたらめな値を使うと復号化(暗号化の逆)が難しくなるので、みなさんが送った情報を盗聴しにくくなるという利点があります 。

練習: 疑似乱数と対話型プログラム

作成するクラスの名前
RandomMessage

5種類のメッセージからでたらめに1つ表示するプログラムを作成しなさい。毎回の表示の後に繰り返すかどうかを入力ダイアログで入力し、繰り返さないことが選択されるまでメッセージを表示し続けること。

  • ヒント: 「練習: 繰り返しの打切り」で作成したプログラムと、疑似乱数の例題を組み合わせたようなプログラムになる

まとめ

今回はこれまでと少し異なるプログラムの形式として、「対話型プログラム」について紹介しました。

対話型プログラムの書き方の1つとして、無限ループと繰り返しの打切りについて紹介しました。これは下記のように無限に処理を繰り返す中で、特定の入力が行われたら繰り返しを終了させるものです。

while (true) {
    <入力の命令>
    if (<特定の値が入力されたか?>) {
        break;
    }
    <入力を利用した処理>
}
<終了時の処理>

上記は一例で、整数以外の値を入力してもかまいません。繰り返し回数を設定せずに、プログラムを使う人が必要に応じてプログラムを終了させられることが重要です。このため、全体の繰り返しは「while (true) {…}」という形式で無限ループにしておき、特定の値が入力されたら「break;」という命令でその無限ループを終了させます。

他のパターンとして、「正しい値が入力されるまで何度も入力させる」というプログラムについても紹介しました。

while (true) {
    <入力の命令>
    if (<正しい値が入力されたか?>) {
        return <入力された値>;
    }
    <正しい値を入力させるためのメッセージ>
}

これにも無限ループの「while (true) {…}」という形式が使われていますが、繰り返しを終了させるために「break;」ではなくメソッドを終了させるreturn文を使用しています。正しい値が入力されたらreturn文でメソッドを終了させますが、そうでない値が入力された際にはメッセージが表示され、もう一度繰り返しの先頭に戻って値を入力しなおします。

最後に、プログラムからでたらめな値を利用するための「疑似乱数」についても紹介しました。これまでのプログラムは同じ入力をすれば同じ結果が得られるものがほとんどでしたが、疑似乱数を利用すると毎回異なる結果を得られるようになります。

int random = (int)(Math.random() * <事象の個数>);

上記で、0以上<事象の個数>未満の値のうち、いずれかをrandomという変数に記憶させます。どの値になるかというのはほぼ同じ確率で、事象の個数が5である場合にはそれぞれ約20%の確率で0, 1, 2, 3, 4となります。

以上が今回の内容 です。ユーザと対話しながら少しずつ目的の処理を実現するプログラムや、ゲームなどのプログラムが書きやすくなったと思います。