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 ループの書き方
- 簡単な文字列操作
- 最小値、最大値の計算
上に書いたものはあくまで私の答えであって、もっと効率の良い書き方やスマートな書き方もあると思いますので、自分の手を動かして試してみてはいかがでしょうか。


