2010-03-01 Google DevFest 2010 Quiz(2)

_ 漢字変換サーバ

すでに記憶が薄れつつありますが思い出しながら書いてみます。

方針

  • 零がでるのは0の時だけなので、先に判定してしまう。
  • その数字が何桁目かわからないことには変換できないので、数列を反転して後ろから見ていく。漢数字も後ろから書いていって最後に反転する。
  • 0 は空文字、1から9は対応する漢数字に変換する。ただし、1については4n+1桁目(n=0, 1, ...)以外は出さない
  • "", "十", "百", "千" が繰り返されるので、現在の桁位置を4で割った余りに応じて対応する文字を書く。ただし数字が0でない時。(逆順に書いているので↑の前に)
  • 4n+1桁目(n=0, 1, ...)に "", "万", "億", "兆" を書く。ただし、4n+1から4(n+1) まで0が続いたら消す(一億万一 は 一億一なので)。

Google の AppEngine にdeploy しましたが、とりあえず数字列から漢数字に変換する部分だけ載せておきます。確認しやすいように、GET のパラメータによって回答用のASCII文字と漢数字を切り替えられるようにしているので最後の方はどうでもいいです。

コードは以下のような感じで。実際はこのファイルにAppEngineのサーブレット用のクラスとUT用のクラスを作りました。

package org.zakky.devfest2010.kansuuji;

import java.util.Arrays;
import java.util.LinkedList;

import com.google.appengine.repackaged.com.google.common.collect.ImmutableBiMap;
import com.google.appengine.repackaged.com.google.common.collect.ImmutableList;
import com.google.appengine.repackaged.com.google.common.collect.ImmutableMap;
import com.google.appengine.repackaged.com.google.common.collect.Iterables;
import com.google.appengine.repackaged.com.google.common.collect.Lists;

public class KansuujiSolver {
    private static final String ZERO = "N";
    private static final String ONE = "Q";
    private static final String TWO = "R";
    private static final String THREE = "D";
    private static final String FOUR = "B";
    private static final String FIVE = "X";
    private static final String SIX = "L";
    private static final String SEVEN = "A";
    private static final String EIGHT = "Z";
    private static final String NINE = "E";
    private static final String JUU = "Y";
    private static final String HYAKU = "J";
    private static final String SEN = "C";
    private static final String MAN = "G";
    private static final String OKU = "H";
    private static final String CHOU = "S";

    // 数字の変換マップ。0は出力する必要がないので空文字列にします
    private static ImmutableMap<Character, String> digitMap__;
    static {
        final String digits = "123456789";
        final String ascii = "" + ONE + TWO + THREE + FOUR + FIVE + SIX + SEVEN
                + EIGHT + NINE;
        assert digits.length() == ascii.length();
        final ImmutableMap.Builder<Character, String> builder;
        builder = ImmutableMap.builder();
        builder.put('0', "");
        for (int index = 0; index < digits.length(); index++) {
            builder.put(digits.charAt(index), "" + ascii.charAt(index));
        }
        digitMap__ = builder.build();
    }
    // 4桁ごとに繰り返される位のための文字("", "十"、"百"、"千")。
    private static ImmutableList<String> BASE_UNITS = ImmutableList
            .copyOf(Arrays.asList("", JUU, HYAKU, SEN));
    // 4桁増えるごとに付加される位のための文字列("", "万"、"億"、"兆")。
    private static ImmutableList<String> EXTRA_UNITS = ImmutableList
            .copyOf(Arrays.asList("", MAN, OKU, CHOU));

    private final String input_;
    private final Translator translator_;

    public KansuujiSolver(String input) {
        this(input, false);
    }

    public KansuujiSolver(String input, boolean asKanji) {
        input_ = input;
        translator_ = asKanji ? k : a;
        if (!isValidInput(input)) {
            throw new IllegalArgumentException("illegal argument: " + input);
        }
    }

    private static boolean isValidInput(String input) {
        if (input.isEmpty()) {
            return false;
        }
        if (KansuujiSolver.getMaxDigits() < input.length()) {
            return false;
        }
        for (int i = 0; i < input.length(); i++) {
            char c = input.charAt(i);
            if (c < '0' || '9' < c) {
                return false;
            }
        }
        return true;
    }

    private static int getMaxDigits() {
        return BASE_UNITS.size() * EXTRA_UNITS.size();
    }

    public String getAnswer() {
        final StringBuilder sb = new StringBuilder(); // 逆順に結果を保持する
        if (isAllZero(input_)) {
            sb.append(ZERO);
            return getAnswer(sb, translator_);
        }

        final LinkedList<Character> string = Lists.newLinkedList();
        for (char c : input_.toCharArray()) {
            string.add(Character.valueOf(c));
        }
        final Iterable<Character> reverseString = Iterables.reverse(string);
        int count = 0;
        boolean blockEmpty = true;
        for (Character d : reverseString) {
            final int baseMod = count % BASE_UNITS.size();
            if (baseMod == 0) {
                sb.append(EXTRA_UNITS.get(count / BASE_UNITS.size()));// 万、億、兆
                blockEmpty = true; // 4桁ごとのブロックの始まりなのでリセット
            }
            if (d != '0') {
                sb.append(BASE_UNITS.get(baseMod)); // 十、百、千
                blockEmpty = false;
            }
            if (d != '1' || baseMod == 0) { // 1, 5, 9, 15桁目が1ではない
                final String dStr = digitMap__.get(d);
                if (!dStr.isEmpty()) {
                    sb.append(dStr);
                    blockEmpty = false;
                }
            }

            if (baseMod == BASE_UNITS.size() - 1 && blockEmpty) {
                final String extraUnitStr = EXTRA_UNITS.get(count
                        / BASE_UNITS.size());
                if (!extraUnitStr.isEmpty()) {
                    // 1億万1対策
                    sb.delete(sb.length() - extraUnitStr.length(), sb.length());
                }
            }
            count++;
        }
        return getAnswer(sb, translator_);
    }

    private static boolean isAllZero(String input) {
        for (int i = 0; i < input.length(); i++) {
            if (input.charAt(i) != '0') {
                return false;
            }
        }
        return true;
    }

    private static String getAnswer(StringBuilder sb, Translator t) {
        final StringBuilder answer = new StringBuilder(sb.length());
        // 変換をかけつつ反転
        for (int index = sb.length() - 1; 0 <= index; index--) {
            answer.append(t.convert(sb.charAt(index)));
        }
        return answer.toString();
    }

    private interface Translator {
        public char convert(char in);
    }

    private static final Translator a = new Translator() {
        public char convert(char in) {
            return in;
        }
    };
    private static final Translator k = new Translator() {
        private final ImmutableBiMap<Character, Character> a2k;
        {
            final String ascii = "" + ZERO + ONE + TWO + THREE + FOUR + FIVE
                    + SIX + SEVEN + EIGHT + NINE + JUU + HYAKU + SEN + MAN
                    + OKU + CHOU;
            final String kanji = "零一二三四五六七八九十百千万億兆";
            assert ascii.length() == kanji.length();

            final ImmutableBiMap.Builder<Character, Character> builder;
            builder = ImmutableBiMap.builder();
            for (int index = 0; index < ascii.length(); index++) {
                builder.put(ascii.charAt(index), kanji.charAt(index));
            }
            a2k = builder.build();
        }

        public char convert(char in) {
            return a2k.containsKey(in) ? a2k.get(in) : in;
        }
    };
}

«前の日記(2010-02-28) 最新