awk でもこのくらいはできるわな


さて、これを awk でやってみました。
しかも、gawk ではなく nawk でやっています。
三大賢者の偉業は凄いです。

いつの間にか設問 1 が変更になっていますが、変更後のものは FizzBuzz と同じですね。

なんらかの文字列を無限で表示する

これは無限ループの練習でしょうか。実際のプログラムでは無限ループを作ることは多くないかもしれませんが、無限ループが作れることは言語としては重要です。
なぜなら、無限に動き続けることができなければ、チューリング完全にはなりません。

awk では以下のように書くことができます。

#! /usr/bin/nawk -f

BEGIN {
    for (;;) {
        print "俺って天才!";
    }
}

なんらかのカウントアップメッセージを1から100まで出すけど、3のときだけアホになる!

微妙なスクリプトですが、最初のループ課題と同じでループを作成することをメインの課題だとすれば、以下のようなものになると思います。

#! /usr/bin/nawk -f

BEGIN {
    for (i = 1; i <= 100; i++) {
        tmp = i;
        if (i ~ /3/) {
            gsub(/3/, "しゃ〜ん", tmp);
        }
        print tmp "万円!";
    }
}

現在時刻にあわせてやる気のでるメッセージを出力

オリジナルの awk には時間取得関数がありません。
そこで date コマンドの出力を getline を介して変数 hour に格納して、これを if 文で処理することにします。

#! /usr/bin/nawk -f

BEGIN {
    date_comm = "date \"+%H\"";
    date_comm | getline hour;
    if (hour == 11) {
        print "もうすぐランチ!がんばれ!";
    }
}

裏技に近く、ビルドされた環境に依存してしまいますが、srand() 関数がエポックタイム (1970 年 1 月 1 日 0:00) からの経過秒数を返すことを利用して以下のように書くこともできます。
ただし、この経過秒数は世界標準時 (UTC) で返しますので、日本であればこれに 9 を足します。

#! /usr/bin/nawk -f

BEGIN {
    now = srand() + srand();
    hour = int((now % (60 * 60 * 24)) / (60 * 60) + 9);
    if (hour == 11) {
        print "もうすぐランチ!がんばれ!";
    }
}

複数の数値を入力させ、昇順に並び替え、最大値と最小値を出す

この課題はいろいろなものが盛りだくさんです。
まず、入力部分ですが、ここでは引数 (ARGV[1]) を指定しなかった場合には、標準入力待ちになることを使っています。
ここで入力されたものを配列 (awk の場合は連想配列ですが) に格納して、これを Quick Sort させています。
awk では Quick Sort はもちろん sort も用意されていませんので、自分で quick_sort() 関数を定義します。
ここで使われている Quick Sort は再帰的に呼び出される Quick Sort を使っています。
最後にソートされた配列を順序良く読み出して表示します。

#! /usr/bin/nawk -f

{
    num[NR] = $0;
}

END {
    quick_sort(num, 1, NR);
    print "----------";
    for (i = 1; i <= NR; i++) {
        print num[i];
    }
    print "----------";
    print "最小値: " num[1];
    print "最大値: " num[NR];
}

# quick_sort - Array Quick Sort
#   input:  array name, minimum index, maximum index
#   output: sorted array
function quick_sort(arr, left, right,     i, last) {
    if (left >= right) {
        return;
    }
    swap(arr, left, left + int((right - left + 1)*rand()));
    last = left;
    for (i = left + 1; i <= right; i++) {
        if (arr[i] < arr[left]) {
            swap(arr, ++last, i);
        }
    }
        swap(arr, left, last);
        quick_sort(arr, left, last - 1);
        quick_sort(arr, last + 1, right);
}

# swap - swap 2 arrays
#   input:  array name, index 1, index 2
#   output: swapped arrays between index 1 and 2
function swap(arr, i, j,    t) {
    t = arr[i];
    arr[i] = arr[j];
    arr[j] = t;
}

簡易電卓

awk の経典でもある「プログラミング言語 AWK」にも出てくる中置記法のプログラムですが、そのままではゼロ除算の処理が分かりにくくなるため、三項演算子をほぐしているだけです。
中身の詳細は「プログラミング言語 AWK」を見ていただけると分かると思います。

#! /usr/bin/nawk -f

NF > 0 {
    f = 1;
    e = expr();
    if (f <= NF) {
        printf("error at %s\n", $f);
    } else {
        printf("\t%.8g\n", e);
    }
}

# term | term [+-] term
function expr(  e) {
    e = term();
    while ($f == "+" || $f == "-") {
        if ($(f++) == "+") {
            e = e + term();
        } else {
            e = e - term();
        }
    }
    return e;
}

# factor | factor [*/] factor
function term(  e) {
    e = factor();
    while ($f == "*" || $f == "/") {
        if ($(f++) == "*") {
            e = e * factor();
        } else {
            tmp_factor = factor();
            if (tmp_factor != 0) {
                e = e / tmp_factor;
            } else {
                print "division by zero.";
                exit;
            }
        }
    }
    return e;
}

# number | (expr)
function factor(  e) {
    if ($f ~ /^[+-]?([0-9]+[.]?[0-9]*|[.][0-9]+)$/) {
        return $(f++);
    } else if ($f == "(") {
        f++;
        e = expr();
        if ($(f++) != ")") {
            printf("error: missing ) at %s\n", $f);
        }
        return e;
    } else {
        printf("error: expected number or ( at %s\n", $f);
        return 0;
    }
}

キーワードを入れてYahoo!の検索結果を出力

nawk にはネットに接続する機能はありませんが、決してこれは不利なことではありません。
もともと Unix という土壌に育った awk は様々なプログラムと連動するためのものですから、既に別のプログラムでできることをわざわざ awk で実行することもないでしょう。

以下のものは、awk の system() 関数で w3m によるテキスト出力を行うものです。
単独では面白くありませんので、使いどころをうまく使いたいものです。

#! /usr/bin/nawk -f

BEGIN {
    url = "http://search.yahoo.co.jp/search?p=";

    getline query < "/dev/stdin";

    w3m_comm = "w3m -dump " url query
    system(w3m_comm);
}

テキストを入れると語尾が赤ちゃん言葉に

これは単に awk の gsub() 関数で行っているだけですが、最後に '1' を付けることで、置換後の文字列を表示させています。
Golf に限らず、Netnews では良く使う手法です。

#! /usr/bin/nawk -f

{
    gsub(/ですよ/, "でちゅよ", $0);
}
1

メールアドレスを入れるとあらかじめ用意されたテンプレートファイルにそのメアドを埋め込んだ上でメール送信

これも awk でやるものではないですが、やり方を知っておくと便利ですよ。

#! /usr/bin/nawk -f

BEGIN {
    subject = "'This is a test'";
    body_file = "template.txt";
    getline address < "/dev/stderr";
    mail_comm = "cat " body_file " | mail -s " subject ;
    system(mail_comm);
}

「(名前)が、(場所)で、(アクション)した」がランダムに組み合わされて出てくるプログラム

数を限定していますが、配列に入れて、それをランダムに抽出する方法です。
特に難しいところはないですが、個人的には int() で整数化するのを良く忘れます。(w

#! /usr/bin/nawk -f

BEGIN {
    name[1] = "ぴかちゅう";
    name[2] = "りざーどん";
    name[3] = "ふしぎそう";
    place[1] = "すまぶら";
    place[2] = "ぽけもんみゅーじあむ";
    place[3] = "どこか";
    action[1] = "きょうそう";
    action[2] = "けんか";
    action[3] = "しあい";

    srand();
    for (i = 1; i <= 10; i++) {
        printf("%s が、", name[int(rand() * 3) + 1]);
        printf("%s で、", place[int(rand() * 3) + 1]);
        printf("%s した\n", action[int(rand() * 3) + 1]);
    }
}

URLを入れるとそのページのはてなブックマーク数が出てくるプログラム

こちらも w3m にお世話になっていますが、そのまま処理を getline を介して awk に渡しています。
クッキーを標準エラー出力に出力してくれるので、一旦全てを標準出力に直してから処理しているところと、日本語を扱う関係上、マシンの文字コードに合わせて nkf で変換しています。

#! /usr/bin/nawk -f

BEGIN {
    hatebu_url = "http://b.hatena.ne.jp/entry/";

    getline url < "/dev/stdin";

    w3m_comm = "w3m -dump " hatebu_url url " 2>&1 | nkf -w";

    while((w3m_comm | getline) > 0) {
        if ($0 ~ /このエントリーをブックマークしているユーザー/) {
            gsub(/[()]/, "", $2);
            print $2;
        }
    }
    close(w3m_comm);
}

実行結果は以下のとおりです。

$ nawk -f 09.awk
http://journal.mycom.co.jp/news/2008/02/08/037/index.html <- 入力
157

ある名前とある名前を入れると相性診断を10段階で出してくれるプログラム

問題作成者の意図としてはハッシュを導入して欲しいのでしょうが、面倒なので、名前の長さだけで判断させています。(w
これなら入れ替わっても同じだし、同じ組み合わせなら必ず同じになるし、問題を満足します。

#! /usr/bin/nawk -f

NR % 2 == 1 {
    name1 = $0;
    n_name1 = length($0);
}
NR % 2 == 0 {
    name2 = $0;
    n_name2 = length($0);
    print (n_name1 + n_name2) % 10;
}

さいごに、これだけ awk で書けるようになってくると、他の言語の魅力を感じなくなってくるのが awk の怖いところでもあります。