Perl/CGI研究室 'PERL-LABO'

Perl/CGI研究室 'PERL-LABO' TOPへ
戻る(History.Back)

ページ閲覧回数をグラフ表示する研究

研究内容

カウントしたページ閲覧回数をランキング&グラフ表示します。

詳細

結果を分かりやすい形で表示する

ページの閲覧回数をカウントすることはできました。 次は、これを分かりやすい形で画面に表示する研究です。 どんなふうに表示したら分かりやすいか、考えてみると、 とりあえず「閲覧回数順に表示する」「閲覧回数を全体の割合(%)で表示する」「閲覧回数をグラフで表示する」 なんてことが思いつきます。今回、この3つに挑戦してみます。

結果

閲覧回数順に表示する

データを読み込むと連想配列にURLと閲覧回数が入ります。 これをそのまま foreach (keys(〜)) とか while (〜 each(〜)) とかで画面に表示すると、 でたらめな順番になってしまいます。 それも、表示するたびに順番が変わっちゃいます。 これじゃ、みにくくて仕方ありません。

そこで、あるルールに従って並び替えて表示するわけです。そのルールですが、URLのアルファベット順というのも 考えられますが、やっぱり閲覧回数順に表示するのがいいですよね。

その方法ですが、これは今までの知識だとちょっと難しそうです。連想配列のキーにURLが、値に閲覧回数が 入っているんですが、URLのアルファベット順に並べるのなら sort(keys(〜)) を使って簡単にできました。 でも、値の方を使って並び替えるのってどうしたらいいのでしょうか?

ここで、久しぶりに新しいPerlの凄い機能が登場します。それは、並び替えを行う関数、sort 関数の別の使い方です。 今まで出てきたのは、sort(配列) で配列をアルファベット順に並べ替えるっていうものでしたが、 sort 関数には他にも次のような使い方があります。

sort { $a <=> $b } 配列

なんか、真ん中に暗号のようなものが付いています。この { } の部分ですが、 これは並べ替えのルールを指定するものです。この { } の中を修正することで、 自分の並び替えたいように配列を並び替えることができるんです。

具体的には、配列の中の2つの値、$a と $b があるとき、 $a $b という順番にするなら 1、 $b $a という順番にするなら -1、 どちらでもいいなら 0、 となるような式を { } の中に書きます。 つまり、2つのデータ $a $b を並べる方法を { } の中で指定するわけですね。 そうすると、sort 関数は、配列の中の2つのデータをこの { } の中の条件式にあてはめて、 条件式の結果に従って並び替えをしてくれるのです。 2つのデータの並び方を指定すれば、自然と配列全体の並び方も決まります。

さて、上の例で <=> というものがありますが、これはまた新しい、便利な 数値比較演算子で、左の方が大きければ -1、右の方が大きければ 1、等しければ 0 となるような演算子です。 つまり、上の例では、値を数値として比較して、大きいものが後になるように配列を並べ替える(ソートする) という意味になります。 <=> は、{ } の中で指定する大小関係を表す -1 0 1 を 返してくれるということで、sort 関数との相性ばっちりです。

ちょっと sort 関数について復習すると、 単に sort(配列)、あるいは括弧は省略できるので sort 配列 と書いた場合、 配列の中身を文字列として考えて、アルファベット順で小さいものが先に、大きいものが後になるように ソートされます。ABCDEF…の順ですね。辞書の順番といえば分かりやすいですね。 ポイントは、sort 関数はそのままだと配列を文字として 考えて文字の順番でソートするっていう点です。 注意すべきは、配列の中身が数値だった場合、数値の大きい順に並ぶわけじゃないっていうことです。 たとえば 1 2 15 という3つのデータがあるとき、sort(配列) とすると 1 15 2 というように数値の順でなくて辞書の順にソートされます。 さて、上の sort { $a <=> $b } 配列 は配列を数値として考えて ソートするっていう意味になりますので、数値の順番で配列をソートしたいときは、 上のやりかたを使えばいいんですね。 数値が小さいものが前に、大きいものが後ろになります。

ちなみに、大きいものを前に、小さいものを後ろにするには、 sort { $b <=> $a } 配列 となりますね。…逆にするとなんか難しいです。 -1 とか 1 とか 0 とか、$a とか $b とか、ややこしいですね。 こういうものは、考え方1つでシュッと理解できたりするんですが、いろいろ考えてみると、 こういうことかな?「$a $b の順番でOKなら 1 となる式を書く」。0 と -1 はこの際無視 (^^; でもこうやって考えると、シュッと理解できるような気がします。

さて sort 関数の新しい使い方についてなんとなく分かったところで、 閲覧回数順にURLを並び替えるっていう話に戻りましょう。 画面に表示するのは URL と 閲覧回数 のペアですが、閲覧回数順に並んだURLの配列があれば、 対応する閲覧回数は連想配列から取得できますので、 その順番に画面に表示するだけになります。ですから、やりたいことは、 URLの配列を閲覧回数順に並び替えるっていうことです。

URLは連想配列のキーになっているんでした。連想配列のキーを配列として 得るには、keys 関数を使えばいいんでしたね。 ページ閲覧回数が入っている連想配列を %data とするとURLの配列は次のようになります。

keys(%data)

この配列をソートします。条件式は後回しにして

閲覧回数順に並んだURL配列 = sort { 後回し } keys(%data);

さて後回しにした条件式ですが、閲覧回数が1番多いURLが配列の先頭にくるようにしましょう。 逆にいうと、閲覧回数が1番少ないURLが最後です。ですから、

閲覧回数順に並んだURL配列 = sort { $aの閲覧回数よりも$bの閲覧回数が少なければ1 } keys(%data);

等しいとき 0 とかの説明は省きました。 $a と $b は、配列 keys(%data) の中の要素、つまりURLが入っています。 URLがあれば、その閲覧回数は連想配列 %data から分かります。 $a の閲覧回数は $data{$a} ですね。つまり

閲覧回数順に並んだURL配列 = sort { $data{$a}よりも$data{$b}が小さければ1 } keys(%data);

これを、数値比較演算子 <=> を使って書きます。 <=> は、「右の方が大きければ 1」ですから、<=> を使う前に 意味を合わせておきましょう。

閲覧回数順に並んだURL配列 = sort { $data{$b}よりも$data{$a}が大きければ1 } keys(%data);

$a と $b をひっくりかえして、意味を逆にしました。いよいよ完成間近!<=> を使って書くと

閲覧回数順に並んだURL配列 = sort { $data{$b} <=> $data{$a} } keys(%data);

じゃーん!閲覧回数の順にURLを並べるという複雑な処理が、この1行でできちゃいました! 処理が複雑なので、なんかホント暗号みたいな1行ですが、 「$a $b の順番でOKなら 1 となる式を書く」ですね。 うーんやっぱりすごいですね、Perl。

閲覧回数を全体の割合(%)で表示する

これは、そのウェブページの閲覧回数を閲覧回数の合計で割って100をかければいいですね。 閲覧回数の合計を求める処理が必要になりますが、今までの知識でできそうです。 例えば、連想配列の値を配列にしてくれる関数 values を使って値を取り出して、 foreach でその和を求めるとかですね。

閲覧回数をグラフで表示する

こちらは、Perl/CGIプログラミングというよりもHTMLの知識が必要になりますね。 グラフで表示するということは、指定した長さの棒を画面に表示することができないと いけません。それができれば、棒の長さはCGIプログラムの中でちょこっと計算すればいいので 難しくないです。で、指定した長さの棒を書く方法ですが、小さな画像ファイルを用意して IMGタグのwidth属性で指定するっていう方法がありますね。でも画像ファイルを作るのが面倒だったり。 そこで、spanタグに背景色とパディングを指定して棒を書くことにしました。

作成したCGIプログラム

printpvgraph.cgi
#!/usr/bin/perl

require 'hashfile.pl';

# データファイル名
$fname = 'pvcounter.txt';

# グラフの棒の最大長さ
$maxwidth = 200;

# データ読み込み
%data = plab::readhashfile($fname);

# 閲覧回数順にソートしたURLの配列を作成
@sorted_keys = sort { $data{$b} <=> $data{$a} } keys(%data);

# 閲覧回数1回に対応するグラフの棒の長さを算出
$maxval = $data{$sorted_keys[0]};
if ($maxval == 0) { $maxval = 1; }
$pixelperval = $maxwidth / $maxval;

# 閲覧回数の合計を算出
@v = values(%data);
$tot = 0;
foreach (@v) {
	$tot += $_;
}

# HTTPヘッダとグラフ用スタイルシート
print << "EOS";
Content-type: text/html

<style type="text/css">
<!--
span.a { background-color: #5050FF; }
-->
</style>
EOS

# HTMLの出力
print "<table cellpadding=5 border=1 align=center>";
print "<tr><th>URL</th><th>回数</th><th>割合</th><th>閲覧回数グラフ</th></tr>";
for (0 .. @sorted_keys - 1) {
	$url = $sorted_keys[$_];			# URL
	$val = $data{$url};			# 閲覧回数
	$width = $val * $pixelperval;		# グラフ長さ
	$per = sprintf("%.1f", $val / $tot * 100);	# 割合
	print "<tr>";
	print "<td nowrap><a href=$url target=_blank>$url</a></td>";
	print "<td align=right>$val</td>";
	print "<td align=right>$per%</td>";
	print "<td><span class=a style=\"padding-left:$width\"> </span></td>";
	print "</tr>";
}
print "</table>";

実行結果

ページビューカウンターを仕込んだページをいくつか用意しましたのでクリックして カウントを増やしてください。 このページでも、ここにカウンターを仕込んでます(見えません) →

pvcounter.cgiを仕込んだページ その1
pvcounter.cgiを仕込んだページ その2
pvcounter.cgiを仕込んだページ その3
pvcounter.cgiを仕込んだページ その4
pvcounter.cgiを仕込んだページ その5

ページ閲覧回数をグラフ表示するには次のリンクをクリックしてください。(今回作ったCGIプログラムです。)

現在の閲覧回数をグラフ表示します。

考察

動きました

今回の研究では、sort 関数の新しい使い方を学べたことが大きかったですね。 閲覧回数順に並べるやり方、最初は分からなくてすごく複雑なことを考えていたんですが、 さすがPerl、便利な機能が用意されていました。素晴らしい。

for (0 .. n) について

HTMLの出力のところででてきた for (0 .. n) というやつですが、 これは 0 から n まで繰り返す、その 0 1 2 …という数字は例によって $_ に入っている、っていう 繰り返し処理を簡単に行う書き方です。ファイルのロック処理のところでちょっとでてきました。 今回それを使ってみました。

sprintf の %.1f について

割合(%)を表示するところで、sprintf 関数を使っています。 そこででてきた %.1f は、実数を小数点以下1桁までで文字列にするっていう意味になります。

分かったこと

  1. sort { 〜 } 配列 というもので配列を指定したルールで並び替えることができます。
  2. { 〜 } の中では $a と $b という変数を使って、$a $b という順番にするなら 1、
    $b $a という順番にするなら -1、 どちらでもいいなら 0、 となるような式を { } の中に書きます。
  3. 「$a $b の順番でOKなら 1 となる式を書く」と考えるとわかりやすい気がします。
  4. <=> という数値比較演算子は、左のが大きければ -1、右のが大きければ 1、等しければ 0 になります。
  5. <=> は sort { 〜 } の 〜 のところに必要なものと同じ値を返すので便利です。
Perl/CGI研究室 'PERL-LABO' TOPへ
戻る(History.Back)

Copyright (c) 'PERL-LABO' All Rights Reserved.  リンクフリーです。