LL Golf Hole 4 - 文章から単語の索引を作る

LL Future の前哨戦として、LL Golf Hole 4 - 文章から単語の索引を作るというお題が出題されています。

こうした Golf の場合には、入力と出力が定義されていないとどこまで頑張れば良いのかが分からないのですが、最初は awk らしくテキストファイルから入力を受け取ります。

#! /usr/bin/gawk -f
# doukaku_index.awk

{
    for (i = 1; i <= NF; i++) {
        word[$i] = word[$i] " " NR;
    }
}

END {
    for (i in word) {
        print i, word[i];
    }
}

awk は文書の処理に適しているだけあって、簡単に記述することができます。 実際には GNU GENERAL PUBLIC LICENSE Version 3 は wget などで取得しておくものだとします。

$ wget http://www.gnu.org/licenses/gpl.txt

このスクリプトを実行します。

$ gawk -f doukaku_index.awk gpl.txt
<snip>
options,  109
<program>  655
long  165 260 286
restriction  393 395

ソートしたい場合には sort コマンドを用います。

$ gawk -f doukaku_index.awk gpl.txt | sort
<snip>
your  14 15 20 29 40 158 161 165 171 171 173 192 232 265 354 411 415 418 \
422 427 429 431 444 508 509 527 545 586 640 661 664 669 670
yourself  504

さて、次に gawk の TCP 通信を使って取得してみます。 また、良い機会なので sort を sort コマンドに双方向パイプで繋げて結果を取得する手法を用いてみます。

#! /usr/bin/gawk -f
# doukaku_index_2.awk

BEGIN {
    # 最初の設定
    FS         = "[ .,\(\)\":<>/]+";
    ORS        = "\r\n";
    base_url   = "www.gnu.org";
    url        = "licenses/gpl.txt";
    proxy      = "localhost";
    proxy_port = "3128";

    # /inet によるアクセスの設定
    http_service = "/inet/tcp/0/" proxy "/" proxy_port;
    get_str      = "GET http://" base_url "/" url;

    # 実際の /inet を介したアクセス
    print get_str " HTTP/1.0" |& http_service;
    print ""                  |& http_service;

    # /inet からの応答の取得
    while ((http_service |& getline) > 0) {
        line++;                 # NR が使えないため

        for (i = 1; i <= NF; i++) {

            # 英単語のみ取得
            if ($i ~ /^[[:alpha:]]+$/) {
                word[$i] = word[$i] OFS line;
            }
        }
    }
    close(http_service);

    # asort() 関数だと連想配列に用いることができないため、
    # 双方向パイプを用いた getline で処理を行う
    sort_cmd = "sort";

    for (idx in word) {

        print idx, word[idx] |& sort_cmd;   # sort コマンドに渡して
    }
    close(sort_cmd, "to");                  # EOF を受けとるためにクローズ

    ORS = "\n";

    # 双方向パイプを再度オープン
    while ((sort_cmd |& getline) > 0) {

        print $0;
    }
    close(sort_cmd);
}

実際に実行してみます。

$ gawk -f doukaku_index_2.awk
<snip>
your  14 15 20 29 40 158 161 165 171 171 173 192 232 265 354 411 415 418 \
422 427 429 431 444 508 509 527 545 586 640 661 664 669 670
yourself  504

たしかにコマンドひとつで取得できるのは嬉しいのですが、やはり awk の TCP を介した通信は決して使いやすいわけではいので、awk らしくありませんし、冗長になってしまいます。

やはり、ここは awk らしく以下のようにまとめたいと思います。

$ GET 'http://www.gnu.org/licenses/gpl.txt' | \
awk 'i=1{while(i++<=NF)w[$i]=w[$i]" "NR}END{for(i in w)print i,w[i]}' | \
sort
<snip>
your  14 15 20 29 40 158 161 165 171 171 173 192 232 265 354 411 415 418 \
422 427 429 431 444 508 509 527 545 586 640 661 664 669 670
yourself  504

awk の UNIX のコマンド群との親和性の高さには驚かされます。

tag_gawk.pngtag_gawk.png