EPOCH@まつやまの例題を解いてみる

EPOCH@まつやまというプログラムイベントがあります。 愛媛大学プログラミングコンテスト「EPOCH@まつやま2009」 - スラッシュドット・ジャパンでも紹介されていたのでご存知の方もいらっしゃると思います。

問題例があり、ここに 10 問の例題があります。 スラッシュドット・ジャパンには「エントリーのために出されている課題がFizzBuzzチックな問題」と書かれていますが、なかなかよくできた例題ですので、得意な言語で解いてみるのも面白いと思います。

ここでは gawk を使って解いてみることにします。 問題を見るのと、問題を実際に解いてみるのとには大きな差がありますので、ぜひ解いてみてください。

問題1『あなたの誕生日は何曜日?』

日数の計算はなかなか難しいので、ここでは gawk の機能に頼りますが、もちろん nawk のように時間関数のないものでも解くことができます。 strftime() を使えば簡単に出てきますが、AWK Users JP :: nawk で時刻取得AWK Users JP :: 17 歳と x 日ジェネレーターなどで使った関数を合わせれば同様のことができます。

#! /usr/local/bin/gawk -f
# epoch_problem_1.awk
# あなたの誕生日は何曜日?
# usage: gawk -f epoch_problem_1.awk

BEGIN {
    getline var < "/dev/stdin";

    split(var, arr_var);
    year    = arr_var[1];
    month   = sprintf("%02d", arr_var[2]);
    day     = sprintf("%02d", arr_var[3]);

    week[1]     = "月曜日";
    week[2]     = "火曜日";
    week[3]     = "水曜日";
    week[4]     = "木曜日";
    week[5]     = "金曜日";
    week[6]     = "土曜日";
    week[7]     = "日曜日";

    week_num = strftime("%u", mktime(year " " month " " day " 00 00 00"));

    print week[week_num];
}

実行してみます。

$ gawk -f epoch_problem_1.awk
2002 1 31
木曜日

$ gawk -f epoch_problem_1.awk
1990 7 7
土曜日

問題2『太りすぎ?やせすぎ?』

if 文の書き方の問題のようなものです。 正しく範囲指定をすれば特に間違えることもないでしょう。

#! /usr/local/bin/gawk -f
# epoch_problem_2.awk
# 太りすぎ?やせすぎ?
# usage: gawk -f epoch_problem_2.awk

BEGIN {
    getline var < "/dev/stdin";

    split(var, arr_var);
    weight  = arr_var[1];
    height  = arr_var[2] / 100;

    bmi = weight / height / height;

    if (bmi >= 30) {
        print "肥満";
    } else if (bmi >= 25 && bmi < 30) {
        print "太りぎみ";
    } else if (bmi >= 18.5 && bmi < 25) {
        print "普通";
    } else {
        print "やせすぎ";
    }
}

実行してみます。

$ gawk -f epoch_problem_2.awk
70 170
普通

$ gawk -f epoch_problem_2.awk
50 110
肥満

$ gawk -f epoch_problem_2.awk
30 100
肥満

問題3 『約数を求めよう!』

約数を求める方法はいろいろあると思いますが、簡単に割りきれるかどうかを確かめる方法で解きました。

#! /usr/local/bin/gawk -f
# epoch_problem_3.awk
# 約数を求めよう!
# usage: gawk -f epoch_problem_3.awk

BEGIN {
    getline var < "/dev/stdin";

    num = var;

    for (i = 1; i <= num; i++) {
        if (num % i == 0) {
            printf("%d ", i);
        }
    }
    print "";
}

解いてみます。

$ gawk -f epoch_problem_3.awk
1234
1 2 617 1234 

$ gawk -f epoch_problem_3.awk
5678
1 2 17 34 167 334 2839 5678 

問題4 『キーワードはいくつある?』

match() 関数でも良いのですが、ここでは gsub() が置換した個数を返すことを利用して解いています。

#! /usr/local/bin/gawk -f
# epoch_problem_4.awk
# キーワードはいくつある?
# usage: gawk -f epoch_problem_4.awk

BEGIN {
    getline var < "/dev/stdin";

    split(var, arr_var);
    str1    = arr_var[1];
    str2    = arr_var[2];

    print gsub(str2, "", str1);
}

答え合わせをします。

$ gawk -f epoch_problem_4.awk
ABC123EFG123HJK 123
2

$ gawk -f epoch_problem_4.awk
ABpanCDpanpanEFpanpa pan
4

問題5 『あなたは合格?不合格?』

これも if 文を間違えずに書ければ解ける問題です。

#! /usr/local/bin/gawk -f
# epoch_problem_5.awk
# あなたは合格?不合格?
# usage: gawk -f epoch_problem_5.awk

BEGIN {
    getline var < "/dev/stdin";

    split(var, arr_var);
    japanese = arr_var[1];
    math     = arr_var[2];
    english  = arr_var[3];
    summary  = japanese + math + english;

    if ((japanese < 30 || math < 30 || english < 30) || summary < 180) {
        print "不合格";
    } else {
        print "合格";
    }
}

答え合わせです。

$ gawk -f epoch_problem_5.awk
76 82 90
合格

$ gawk -f epoch_problem_5.awk
96 21 88
不合格

$ gawk -f epoch_problem_5.awk
82 73 51
合格

問題6 『カレンダ』

本例題にはいくつかの時間問題が出題されていますが、時間や日数の計算はちきんと押さえておくべき問題ということでしょうか。 曜日を割り出す方法はAWK Users JP :: nawk で時刻取得でも使われています。

#! /usr/local/bin/gawk -f
# epoch_problem_6.awk
# カレンダ
# usage: gawk -f epoch_problem_6.awk

BEGIN {
    getline var < "/dev/stdin";

    split(var, arr_var);
    week    = arr_var[1];
    day     = arr_var[2];

    str_week = "Sun Mon Tue Wed Thu Fri Sat";
    split(str_week, week_name);

    for (i in week_name) {
        if (week_name[i] == week) {
            init_week_num = i;
        }
    }

    week_num = ((day + init_week_num) % 7 - 1) ? ((day + init_week_num) % 7 - 1) : 7;
    print week_name[week_num];
}

答え合わせをします。

$ gawk -f epoch_problem_6.awk
Sun 17
Tue

$ gawk -f epoch_problem_6.awk
Tue 20
Sun

問題7 『最小値~平均値~最大値』

最小値、平均値、最大値の計算はさまざまな場面で使うことがあるので、サクサク書くことができると便利です。 awk には四捨五入がないので、AWK Users JP :: 切り下げ、切り上げ、四捨五入から round() 関数を使っています。

#! /usr/local/bin/gawk -f
# epoch_problem_7.awk
# 最小値~平均値~最大値
# usage: gawk -f epoch_problem_7.awk

BEGIN {
    getline num < "/dev/stdin";
    getline var < "/dev/stdin";

    if (num <= 0) {
        exit;
    }

    split(var, arr_var);

    min = arr_var[1];
    max = arr_var[1];
    for (i = 1; i <= num; i++) {
        if (min > arr_var[i]) {
            min = arr_var[i];
        }
        if (max < arr_var[i]) {
            max = arr_var[i];
        }
        sum = sum + arr_var[i];
    }

    average = sprintf("%.1f", round(sum / num * 10) / 10);

    print min "〜" average "〜" max;

}

# 小数部の四捨五入
#   in:     数値 num
#   out:    数値 num の小数部を四捨五入したもの
function round(num) {
    if (num > 0) {
        return int(num + 0.5);
    } else {
        return int(num - 0.5);
    }
}

答え合わせです。

$ gawk -f epoch_problem_7.awk
5
2 1 9 -3 6
-3〜3.0〜9

$ gawk -f epoch_problem_7.awk
17
2 -3 6 0 11 3 2 1 8 44 12 25 -30 91 17 26 25
-30〜14.1〜91

問題8 『飛行時間は何時間?』

問題そのものの長さが長くなると難しそうに感じてしまいますが、実際に手間のかかる問題です。 一度 Epoch Time (Unix Time) に変換して計算してます。

#! /usr/local/bin/gawk -f
# epoch_problem_8.awk
# 飛行時間は何時間?
# usage: gawk -f epoch_problem_8.awk

BEGIN {
    getline var < "/dev/stdin";

    split(var, arr_var);
    from_city   = arr_var[1];
    from_month  = arr_var[2] "";
    from_day    = arr_var[3] "";
    from_hour   = arr_var[4] "";
    from_min    = arr_var[5] "";
    to_city     = arr_var[6];
    to_month    = arr_var[7] "";
    to_day      = arr_var[8] "";
    to_hour     = arr_var[9] "";
    to_min      = arr_var[10] "";

    gap["TO"] = "0,0";
    gap["NE"] = "-14,1";
    gap["DE"] = "-14,1";
    gap["SI"] = "-15,1";
    gap["VA"] = "-17,1";
    gap["SY"] = "1,1";
    gap["MO"] = "-6,1";

    from_time = mktime("2009 " from_month " " from_day " " from_hour " " from_min " 00");
    if (substr(gap[from_city], length(gap[from_city])) == 1 && \
        from_month + 0 >= 4 && from_month + 0 <= 10) {
        split(gap[from_city], arr_gap, ",");
        gap[from_city] = (arr_gap[1] + 1) * 3600;
    } else {
        split(gap[from_city], arr_gap, ",");
        gap[from_city] = arr_gap[1] * 3600;
    }

    to_time = mktime("2009 " to_month " " to_day " " to_hour " " to_min " 00");
    if (substr(gap[to_city], length(gap[to_city])) == 1 && \
        to_month + 0 >= 4 && to_month + 0 <= 10) {
        split(gap[to_city], arr_gap, ",");
        gap[to_city] = (arr_gap[1] + 1) * 3600;
    } else {
        split(gap[to_city], arr_gap, ",");
        gap[to_city] = arr_gap[1] * 3600;
    }

    elapsed_time = (to_time - gap[to_city]) - (from_time - gap[from_city]);
    elapsed_hour = elapsed_time / 3600;
    elapsed_min  = (elapsed_hour - int(elapsed_hour)) * 60;

    print int(elapsed_hour) "時間" elapsed_min "分";
}

答え合わせをしてみますが、問題が間違えていますね。 「では,モスクワ7月23日19:20発,東京着7月24日10:00時着の飛行時間は?」は「では,モスクワ7月23日19:30発,東京着7月24日10:00時着の飛行時間は?」ですね。

$ gawk -f epoch_problem_8.awk
TO 07 19 13 00 DE 07 19 12 30
12時間30分

$ gawk -f epoch_problem_8.awk
MO 07 23 19 30 TO 07 24 10 00
9時間30分

問題9 『部分文字列の繰り返し』

文字列操作は awk の得意とするところです。

#! /usr/local/bin/gawk -f
# epoch_problem_9.awk
# 部分文字列の繰り返し
# usage: gawk -f epoch_problem_9.awk

BEGIN {
    getline var < "/dev/stdin";

    split(var, arr_var);
    str     = arr_var[1];
    num1    = arr_var[2];
    num2    = arr_var[3];

    str = substr(str, num1);

    for (i = 1; i <= num2; i++) {
        cont_str = cont_str str;
    }

    print cont_str;
}

答え合わせをします。

$ gawk -f epoch_problem_9.awk
ABCDEFG 5 3
EFGEFGEFG

$ gawk -f epoch_problem_9.awk
Matsuyama 6 4
yamayamayamayama

問題10 『首位打者は誰?』

いよいよ最後の問題ですが、先ほどの最大値を求める問題と同じですが、表示するべきものに注意してください。

#! /usr/local/bin/gawk -f
# epoch_problem_10.awk
# 首位打者は誰?
# usage: gawk -f epoch_problem_10.awk

{
    rate[$1] = $3 / $2;
}

END {
    for (i in rate) {
        if (max < rate[i]) {
            max = rate[i];
            max_name = i;
        }
    }

    print max_name;
}

はじめて awk らしい書式ですが、答え合わせをして終わりにします。

$ cat sample_1.txt
Ogasawara 390 125
Tani 378 122
Saeki 258 84
Aoki 357 126
Ramirez 374 129

$ gawk -f epoch_problem_10.awk sample_1.txt
Aoki

$ cat sample_2.txt
Ohmura 370 120
Morimoto 405 127
Cabrera 289 93
Rick 304 101
Rhodes 345 108

$ gawk -f epoch_problem_10.awk sample_2.txt
Rick

思ったこと

というわけで例題を 10 問解いてみましたが、いくつか試されていつことがあるのに気がつきます。

  • 時間計算
  • if 文の優先順位
  • for ループまたは while ループの書き方
  • 簡単な文字列操作
  • 最小値、最大値の計算

上に書いたものはあくまで私の答えであって、もっと効率の良い書き方やスマートな書き方もあると思いますので、自分の手を動かして試してみてはいかがでしょうか。

tag_gawk.pngtag_gawk.png