漢数字から英数字

ちょうど今 RubyKaigi2008 をやっていますが、その中で出された anarchy golf より Japanese numeral for Ruby kaigi を取り上げてみました。 ただし、ゴルフはやりません。

同じような問題が以前 LLDN の中の「君ならどう書く」のお題として出されました。 漢数字で電卓を作れという物です。 これを解いたことがある人なら比較的簡単に解けたのではないでしょうか?

しかも、この問題は一万未満であるため、非常に簡単に解くことができます。 もっとも awk の場合には eval がないので、そちらの方が大変ですが・・・。

#! /usr/bin/gawk -f
# japanese_numeral_for_ruby_kaigi.awk
# http://tinyurl.com/6rb29l

{
    if ($0 ~ /^[千百十]/) {
        $0 = "一" $0;
    }
    sub(/千百/, "千一百", $0);
    sub(/千十/, "千一十", $0);
    sub(/百十/, "百一十", $0);
    ascii_num_str = "";
    n_japanese_num = split($0, japanese_num, "");
    for (i = 1; i <= n_japanese_num; i++) {
        if (japanese_num[i] == "一") {
            ascii_num_str = ascii_num_str "+1";
        }
        if (japanese_num[i] == "二") {
            ascii_num_str = ascii_num_str "+2";
        }
        if (japanese_num[i] == "三") {
            ascii_num_str = ascii_num_str "+3";
        }
        if (japanese_num[i] == "四") {
            ascii_num_str = ascii_num_str "+4";
        }
        if (japanese_num[i] == "五") {
            ascii_num_str = ascii_num_str "+5";
        }
        if (japanese_num[i] == "六") {
            ascii_num_str = ascii_num_str "+6";
        }
        if (japanese_num[i] == "七") {
            ascii_num_str = ascii_num_str "+7";
        }
        if (japanese_num[i] == "八") {
            ascii_num_str = ascii_num_str "+8";
        }
        if (japanese_num[i] == "九") {
            ascii_num_str = ascii_num_str "+9";
        }
        if (japanese_num[i] == "千") {
            ascii_num_str = ascii_num_str "*1000";
        }
        if (japanese_num[i] == "百") {
            ascii_num_str = ascii_num_str "*100";
        }
        if (japanese_num[i] == "十") {
            ascii_num_str = ascii_num_str "*10";
        }
    }
    sub(/^\+/, "", ascii_num_str);
    system("echo '" ascii_num_str "' | bc");
    system("echo '" ascii_num_str "' | gawk -f calc.awk");
}

かなりダラダラと書いていますが、ダラダラと書ける理由は一万未満であるためです。 実際に実行してみますが、最後で bc コマンドに渡すものと「プログラミング言語 AWK」にも掲載されている calc.awk で処理するものと併記してあります。

$ gawk -f japanese_numeral_for_ruby_kaigi.awk
三千百四十一
3141
3141

一万未満は括弧も必要としない単純な足し算に還元できますので、これを利用して数式化し、それを bc コマンドまたは calc.awk に投げています。

ちなみに以下が calc.awk ですが、これは「プログラミング言語 AWK」のものを多くの awker により強化されたものです。

#! /usr/local/bin/xgawk -f
# calc3 - infix calculator - derived from calc3 in TAPL, chapter 6.
# by Kenny McCormack, Mon 3 Jan 2000
# modified by Alan Linton, $Date: 2000/01/06 21:37:36 $, $Revision: 1.16 $
# modified by Hirofumi Saito.

{
    print eval($0);
}

# The rest is functions...
function eval(s   ,e) {
    _S_expr = s;
    gsub(/[ \t]+/, "", _S_expr);
    if (length(_S_expr) == 0) {
        return 0;
    }
    _f = 1;
    e = _expr();
    if (_f <= length(_S_expr)) {
        printf("An error occurred at %s\n", substr(_S_expr, _f));
    } else {
        return e;
    }
}

function _expr(    var,e) { # term | term [+-] term
    if (match(substr(_S_expr, _f), /^[A-Za-z_][A-Za-z0-9_]*=/)) {
        var = _advance();
        sub(/=$/, "", var);
        return _vars[var] = _expr();
    }
    e = _term();
    while (substr(_S_expr, _f, 1) ~ /[+-]/) {
        e = substr(_S_expr,_f++,1) == "+" ? e + _term() : e - _term();
    }
    return e;
}

function _term(    e) { # factor | factor [*/%] factor
    e = _factor();
    while (substr(_S_expr,_f,1) ~ /[*\/%]/) {
        _f++;
        if (substr(_S_expr, _f-1, 1) == "*") {
            return e * _factor();
        }
        if (substr(_S_expr,_ f-1, 1) == "/") {
            return e / _factor();
        }
        if (substr(_S_expr,_f-1,1) == "%") {
            return e % _factor();
        }
    }
    return e;
}

function _factor(   e) { # factor2 | factor2^factor
    e = _factor2();
    if (substr(_S_expr, _f, 1) != "^") {
        return e;
    }
    _f++;
    return e ^ _factor();
}

function _factor2(    e) { # [+-]?factor3 | !*factor2
    e = substr(_S_expr, _f);
    if (e ~ /^[\+\-\!]/) { #unary operators [+-!]
        _f++;
        if (e ~ /^\+/) {
            return +_factor3(); # only one unary + allowed
        }
        if (e ~ /^\-/) {
            return -_factor3(); # only one unary - allowed
        }
        if (e ~ /^\!/) {
            return !(_factor2() + 0); # unary ! may repeat
        }
    }
    return _factor3();
}

function _factor3(   e,fun,e2) { # number | varname | (expr) | function(...)
    e = substr(_S_expr, _f);

    #number
    if (match(e, /^([0-9]+[.]?[0-9]*|[.][0-9]+)([Ee][+-]?[0-9]+)?/)) {
        return _advance();
    }

    #function()
    if (match(e, /^([A-Za-z_][A-Za-z0-9_]+)?\(\)/)) {
        fun = _advance();
        if (fun ~ /^srand()/) {
            return srand();
        }
        if (fun ~ /^rand()/) {
            return rand();
        }
        printf("error: unknown function %s\n", fun);
        return 0;
    }

    # (expr) | function(expr) | function(expr,expr)
    if (match(e, /^([A-Za-z_][A-Za-z0-9_]+)?\(/)) {
        fun=_advance();
        if (fun ~ /^((cos)|(exp)|(int)|(log)|(sin)|(sqrt)|(srand))?\(/) {
            e = _expr();
            e = _calcfun(fun, e);
        } else if (fun ~ /^atan2\(/) {
            e = _expr();
            if (substr(_S_expr, _f, 1) != ",") {
                printf("error: missing , at %s\n", substr(_S_expr, _f));
                return 0
            }
            _f++;
            e2 = _expr();
            e = atan2(e, e2);
        } else {
            printf("error: unknown function %s\n", fun);
            return 0;
        }
        if (substr(_S_expr, _f++, 1) != ")") {
            printf("error: missing ) at %s\n", substr(_S_expr, _f));
            return 0;
        }
        return e;
    }

    #variable name
    if (match(e, /^[A-Za-z_][A-Za-z0-9_]*/)) {
        return _vars[_advance()];
    }

    #error
    printf("error in factor: expected number or ( at %s\n",
           substr(_S_expr, _f));
    return 0;
}

function _calcfun(fun, e) { #built-in functions of one variable
    if (fun == "(") {
        return e;
    }
    if (fun == "cos(") {
        return cos(e);
    }
    if (fun == "exp(") {
        return exp(e);
    }
    if (fun == "int(") {
        return int(e);
    }
    if (fun == "log(") {
        return log(e);
    }
    if (fun == "sin(") {
        return sin(e);
    }
    if (fun == "sqrt(") {
        return sqrt(e);
    }
    if (fun == "srand(") {
        return srand(e);
    }
}

function _advance(    tmp) {
    tmp = substr(_S_expr, _f, RLENGTH);
    _f += RLENGTH;
    return tmp;
}

そう言えば、最近は awker というよりも、awk enthusiasts と言うみたいですね。 非常に発音しづらいです。