【paiza】出題解答用の自分用ユーティリティを作った【Java】


■PaizaUtilityのJava版を作りました。

以前公開した 【paiza】出題解答用の自分用ユーティリティを作った【C#】 のJava版です。

Javaは xxx4j と言う命名が一般的らしいので、PaizaUtil4Jと名付けました。
(log4jとか、launch4jとか、twitter4jとかもそうですね。)

改訂メモ:

  • 【version1】C#版をベースにJava版を実装。
  • 【version2】DebugIOプロキシの#readLineのバグ修正
  • 【version3】逐次的文字列リテラル代用の為、ファイル入力に実装変更。

【version1, 2】基本機能だけ取り敢えずJavaに移植しました。

機能性や使い方はC#版と同じなので、今回は省略します。

(*'▽') 説明が必要な人は、以前書いた記事の方を参照してね。

PaizaUtility.java

public class PaizaUtility
{
    public interface ITestIO
    {
        String readLine();

        void writeLine(String line);
    }

    public static class ConsoleProxy implements ITestIO
    {
        private final Scanner in = new Scanner( System.in );

        @Override
        public String readLine()
        {
            return in.nextLine();
        }

        @Override
        public void writeLine(String line)
        {
            System.out.println( line );
        }
    }

    static ITestIO io;

    // static ctor
    static {
        io = new ConsoleProxy();
    }

    public static Iterable<String> readArgs()
    {
        String s = io.readLine();
        int n = Integer.valueOf( s );

        return readArgs( n );
    }
    public static Iterable<String> readArgs(final int n)
    {
        List<String> args = new ArrayList<String>();

        for ( int i = 0; i < n; i++ )
        {
            String arg = io.readLine();

            args.add( arg );
        }

        return args;
    }

    public interface IFunction<TResult, TParameter>
    {
        TResult invoke( TParameter parameter );
    }
    public static Iterable<String> readArgs( IFunction<Integer, String> parseHeaderRecord ) {
        String header = io.readLine();
        int n = parseHeaderRecord.invoke( header ).intValue();

        return readArgs( n );
    }


    // static class.
    private PaizaUtility() { }
}
Do.java(paiza出題の解答ロジックを実装するところ)

public class Do
{
    public static void answer()
    {
//      // IFunction デリゲートをラムダで書くとこんなコードになる。
//      Iterable<String> args = PaizaUtility.readArgs(
//              s -> Integer.parseInt( s )
//      );
        Iterable<String> args = PaizaUtility.readArgs();

        for ( String arg : args )
        {
            PaizaUtility.io.writeLine( arg );
        }
    }
}
Program.java(プログラムのメインエントリポイント)

public class Program
{
    public static void main( String[] args )
    {
        PaizaUtility.io = new DebugIO();

        Do.answer();
    }
}

で、肝心のDebugIOクラスの実装がまだ固まってないんですよね。

◇現状でのDebugIOクラスの仮実装

Program.DebugIO
    private static class DebugIO implements ITestIO
    {
        // java には C# みたいな逐次的文字列リテラル記法が無いからダルい、どーすりゃいいんだこれ。
        private final String[] source = {
                "2",
                "hello",
                "world",
        };

        private int index = 0;

        @Override
        public String readLine()
        {
            return index < source.length ? source[index++] : null;
        }

        @Override
        public void writeLine(String line)
        {
            System.out.println( line );
        }
    }

◇DebugIOクラスで困った事。

C#では 逐次的文字列リテラル という構文仕様があるんですが、Javaにはそれ相当の機能が無いんですよね。
このせいで、C#版だとpaizaの出題パラメータをそのままWeb画面からコピペで使えたのが、Java版だと同じ手が使えない。
さて、どうしたものか。
(ファイルを置いといてパス通してFileReadと言うのが手っ取り早い逃げ道だと思うけど、出来ればファイル使わずにコードベースでやりたいんだけど・・・無理かな)

◇あんまり関係ないけど

コードスタイルがC#ライクでごめんね!!
ぼくはもともとJavaじゃなくC#出身だし、言語的にもJavaよりC#のが好きなんだ!!

◇Java版の問題点

static class 問題。

C#ではstatic classを作れるけど、Javaではstatic classを作れない。

Javaではstatic classを作れないので、外側の(Javaで言えばエンクロージング側の)クラスにstaticキーワードを付けていない。
このせいで、具体的にはPaizaUtilityとDoの2クラスにstaticキーワードを付けてないので、このままコピペしたらコンパイルエラーになる。

強引にこれを解決するとしたら、問題の2クラスを更にダミーのエンクロージングクラスで包んでやって、inner staticメンバに落としてやればいいんだけど、、、なんだかなぁ、、、。

◇バグってました><

Scannerクラスの使い方を間違っていたようで、普通にバグっててpaizaで使えませんでした。
(正確には、使えないケースがあった)

ConsoleProxy(修正前)

        @Override
        public String readLine()
        {
            return in.next();
        }
ConsoleProxy(修正後)

        @Override
        public String readLine()
        {
            return in.nextLine();
        }

なんてこったい(´・ω・`)

Javaで標準IOなんてそうそうやらないから、paiza公式の 値取得・出力サンプルコード をそのまま実装したらハメられました。
よく確認しなかった自分が悪いんですけどね、、、。

参考情報:
似たような所でハマッてる人がいて参考になりました。

JavaのScannerとか.nextLine()の挙動をよく分かってなかった話

【version3】逐次的文字列リテラル対応(ファイル入力化)

DebugIO(コンストラクタのみ抜粋)

        public DebugIO()
        {
            super();
            this.source = readDataSource();
        }
        private static String[] readDataSource()
        {
            // 取り敢えず相対パスでテキストファイルから読み込む事にする。
            Path path = Paths.get( "dat/datasource.txt" );

            try
            {
                return Files.lines( path ).toArray( String[]::new );
            }
            catch ( IOException ex )
            {
                // 絶対有り得ないケースだから何でも良いんだけど取り敢えず空にしとく。
                ex.printStackTrace();
                return new String[]{};
            }
        }
dat/datasource.txt
3
hoge
moge
piyo
実行結果
hoge
moge
piyo

■ソリューション一式はこちら

ソリューション一式はGitHubに置いてあります。
今後、コメント追加したり、機能改良したり、色々弄ってく予定。