交換可能な発送ルールを比較する

JavaBeginner
オンラインで実践に進む

はじめに

発送窓口は料金を取得できるようになりましたが、比較メソッドはまだ最安値のルールを返しません。ルールオブジェクトを比較し、同額の最安値をすべて残し、複数の荷物の完全な見積もりファイルを保存します。

この実験は独立して初期化され、前の guided 実験で完成した 2 種類のルール実装が用意されています。前提は、その実験で学んだインターフェース、適用条件、整数の段階料金です。新しい料金アルゴリズムを考えるのではなく、既存ルールに異なるパラメータのインスタンスを追加します。料金はすべて架空です。所要時間は作成者による見積もりです。

インターフェースを通して比較する

このステップでは、比較器が見たことのない新実装も含め、渡された任意の ShippingRule に対応する比較メソッドを実装します。

WebIDE タブを開き、メニューから Terminal → New Terminal を選んで統合ターミナルを開きます。以下のコマンドはこの WebIDE ターミナルで実行してください。各コマンドは同じシェルで動き、cd はそのシェルの作業ディレクトリを変更します。ターミナルを開き直した場合は、cd をもう一度実行します。JDK とソースの枠組みは準備済みで、ビルドツールのインストールは不要です。

cd /home/labex/project/parcels
./quote.sh quote 1500

Town と Steps はどちらも 600 を表示します。この通常の同額例から、最安ルールを 1 つしか残さないと有効な選択肢を失うことがわかります。公開契約と入力条件の全体は次のとおりです。

公開契約は ShippingRule.id() と ShippingRule.quote(Parcel)、および Quote.eligible() と Quote.cents() です。RuleFactory.flat(String, int, long) と RuleFactory.stepped(String, int, int, long) がルールオブジェクトを生成し、QuoteEngine.bestRules(Parcel, List<ShippingRule>) が最安値となるすべてのルール ID を返します。これらのシグネチャにより、同じ呼び出し側から異なる実装を利用できます。実装クラスの構成を変え、その生成のために RuleFactory を変更してもかまいません。公開メソッドと提供されたコマンド・出力形式を維持してください。ファクトリ内部のクラス名や使用するエディタは採点対象ではありません。

対応する荷物の重量は 1~1,000,000 グラムの整数です。ルールの重量上限と段階幅も 1~1,000,000 の整数で、固定料金または各段階の料金は 0~1,000,000 セントの整数です。条件を満たすゼロセントの見積もりも有効です。これは Quote.unavailable() とは異なります。後者は eligible() が false で、内部に保存された金額を比較に使ってはいけません。計算した金額は long に保存するため、大きな有効な積でも 32 ビットの int がオーバーフローしません。すべての料金、人物、荷物名は架空の練習データであり、実際の配送会社のリアルタイム料金ではありません。

比較器は List<ShippingRule> を受け取ります。要素ごとに実装が異なっていても、拡張 for ループですべてのオブジェクトを訪問できます。ルール名を調べたり、instanceof で業務ロジックを再現したりせず、quote(parcel) を呼び出します。

最安ルールのリストを空にし、最大の long 値である Long.MAX_VALUE を比較の番兵値として使います。適用不可の見積もりは continue で飛ばします。厳密に安い料金が見つかれば以前の最低額が変わるため、clear() で古い最安ルールを削除します。同額ならその ID を追加します。これらは別々の if 文です。新しく最低額になったルールも追加する必要があるためです。適用可能なものがなければリストは空のままです。

次のコマンドは Java ソースファイルを置き換えます。cat は 2 行の JAVA の間のテキストを > の後のパスへ書き込みます。開始マーカーの引用符は、Java コード内でのシェルの展開を防ぎます。最後の JAVA も単独の行として含め、ブロック全体を貼り付けてください。同じファイルを WebIDE で編集・保存し、WebIDE ターミナルに戻る方法でもかまいません。不完全な貼り付けで継続入力のプロンプトが出た場合は、Ctrl+C を押してから完全なブロックを貼り直してください。

cat > src/QuoteEngine.java <<'JAVA'
import java.util.ArrayList;
import java.util.List;
public final class QuoteEngine {
    public static List<String> bestRules(Parcel parcel, List<ShippingRule> rules) {
        List<String> winners = new ArrayList<>();
        long lowest = Long.MAX_VALUE;
        for (ShippingRule rule : rules) {
            Quote quote = rule.quote(parcel);
            if (!quote.eligible()) continue;
            if (quote.cents() < lowest) {
                lowest = quote.cents();
                winners.clear();
            }
            if (quote.cents() == lowest) winners.add(rule.id());
        }
        return winners;
    }
}
JAVA
./quote.sh batch data/parcels.tsv data/rates.tsv

batch は荷物 TSV のパスと料金 TSV のパスを受け取ります。荷物の行は ID、表示名、整数グラム数です。出力では各荷物にすべての見積もりと BEST 行をまとめます。UNAVAILABLE はそのルールが運べないこと、BEST NONE はどのルールも運べないことを示します。適用可能な見積もりのない有効なバッチも、ステータス 0 で正常終了します。

P02 の BEST には Town と Steps の両方が必要です。P03 は 1 グラム重く、Town だけが最安です。P01 と P02 はどちらも Gift ですが、ID が異なるため見積もりは分かれたままです。P04 に適用可能なルールはありません。利用不可の結果も含め、すべての荷物とすべてのルールを残してください。

別のルールを登録して完全な見積もりを残す

このステップでは、比較器を変更せずに固定料金ルールの新インスタンスを登録し、窓口が後で読める結果を保存します。

引き続き WebIDE ターミナルの /home/labex/project/parcels で操作します。料金読み取り処理は RuleFactory で指定された実装を構築します。そのため flat 行をもう 1 行追加すれば、独立したパラメータを持つ別のオブジェクトが作られます。元の data/ ファイルは変更しません。cp は作業用コピーを作り、printf はテキストを書きます。\t はタブ、\n は行末を表します。>> はファイルを置き換えず末尾へ追加します。

cp data/rates.tsv work/rates.tsv
printf 'flat\tRelay\t6000\t600\n' >> work/rates.tsv
./quote.sh batch data/parcels.tsv work/rates.tsv

Relay は 6,000 グラムまでを含めて適用可能で、料金は 600 セントです。P02 の最安ルールは 3 つの同額になりますが、6,500 グラムの P04 は依然として見積もりがありません。登録するルール集合を変えても、元の比較コードはそのまま動作しました。

出力そのものが実際の見積もり成果物です。> は標準出力をファイルに保存し、同名ファイルがあれば置き換えます。Java が入力を検証する前にシェルが出力ファイルを開くため、有効なバッチだけを保存してください。

./quote.sh batch data/parcels.tsv work/rates.tsv > work/quotes.tsv
cat work/quotes.tsv

各 ID には PARCEL 行を 1 つ、登録ルールごとの QUOTE 行、適用可能な最安 ID をカンマで区切った BEST 行、または NONE が必要です。金額は整数セントのままです。ルールと最安 ID は登録リストの順序でもかまいません。重要なのはメンバーと関係の完全性です。

無効な重量と、重量は有効だが見積もりがない荷物は異なります。提供された無効データにはゼログラムの BAD があります。パーサーは見積もりを表示する前にバッチ全体を検証します。無効なバッチはステータス 2 で終了し、標準エラー出力に説明を出し、見積もり行を一切出しません。意図した失敗を試すとき、正常なファイルへリダイレクトしないでください。

./quote.sh batch data/invalid-parcels.tsv work/rates.tsv

INVALID_WEIGHT BAD が表示されるはずです。有効な荷物ファイルに戻して復帰します。

./quote.sh batch data/parcels.tsv work/rates.tsv

元の荷物・料金記録と正常な work/quotes.tsv は保たれています。コンパイルエラーなら指示されたファイルを修正・保存し、パスの誤りなら cd /home/labex/project/parcels をやり直して再実行します。起動スクリプトは毎回、保存したソースを再構築し、前の実験と同じく UTF-8 を明示的に使います。

チェックは変更していないコピーを一時的なテストデータ上でコンパイルし、ほかの有効なパラメータやインターフェース実装も試します。現場の不足した結果を補ったり、作業領域を書き直したりはしません。これは学習環境での機能評価であり、管理者によるファイル偽装を防ぐ仕組みではありません。

まとめ

配送会社ごとの分岐を作らずインターフェースオブジェクトを比較し、同額最安値をすべて残して、見積もりがない有効な状態も明示できました。設定したルールの追加には同じ処理を再利用しました。保存ファイルには、各荷物の ID と完全な選択肢が残ります。