国別メダル数ランキングを俺基準でランク付けし直すJS/Boookmarklet作ったった

Yahoo!には、ロンドンオリンピック企画として、特集ページが用意されている。そこには、オリンピックに関するニュースや選手情報などが掲載されており、もちろん、各国の今までの獲得メダル数まで記録されている。しかしこの獲得メダル数のページ、ランキング形式で掲載されているのだが、そのランキング方法が少しおかしい。そこで、オレオレ手法でランキングし直すJS/Bookmarkletを書くことにした。

動機

発端は、「Yahoo!のメダルランキングの順位付け方法が気に喰わない。」というどうでもいい理由。現状では、「金メダル取得数でソートして、同じ数ならその中で銀メダル取得数でソート、それも同じなら銅メダルでソート」というやり方をしているようだ。アルゴリズム的には「銅でソートして、銀でソートしなおして、最後に金でソートしている」となるのだろうか。しかしこれには大きな欠陥がある。例えば、「金メダルは1枚取ったが、それ以外は取れてない国」と「金メダルは取ってないけど、銀メダルなら10枚、銅メダルなら30枚取った国」を比較した時に、明らかに後者のほうがスゴイのに、この手法では前者のほうが上位に踊り出てしまう。コレはいけない。

アルゴリズム

アルゴリズムというほどでもないが、オレオレ手法では「各メダルの取得枚数をポイントに換算し、そのポイント数の高い順で並び替える」というもの。例えば、「金:3点、銀:2点、銅:1点」の場合、「金1枚、銀2枚、銅3枚」の国は「10点」と換算する。この配点は適当に決めるしかないのだが、(俺的に)より直感的になるようにうまく調整するのがよろしかろう。俺は「金:7点、銀:3点、銅:1点」にした。下位メダルの2倍+1だ。
実はこのページ、2ページ構成になっているので、正確にランキングし直すには、次ページのコンテンツもこのページに呼び込まなければならない。流行りのajaxを用いて、次ページのTableタグを1ページ目のTableタグとマージする必要がある。
各国の情報はTRタグでまとめられている。そしてその情報(獲得メダル数等)はすべてテキストデータだ。これらを一度抽出、数値化して、その場でポイント換算を行う。換算したポイントはそのTRタグの$("tr").data("point");に格納する。あとでコレを用いてソートするためだ。jQuery便利っすね!
ひと通りポイント換算が終わったら早速ソートだ。先ほど格納したdata("point")を元にランキングし直す。この際、もし同じポイントの国があったら、そこは現状のランキングで上位の国が上に来るようにソートする。とはいえ、同ポイントは同順位にしたいので、この処理はあくまで表示上の問題である。
最後にランキングナンバーの差し替えを行わなければならない。TRタグレベルで並び替えているので、このまま反映するとランキングナンバーが降順にならないのである。既にソート自体は終わっているのでその順番になるように振り直すだけだが、できれば同ポイントは同順位にしたいのでちょっとゴニョゴニョしている。あとは、既についてるclass属性を取り、振り直せば完成である。

gist

書いたJSをgistにあげた。例によって例のごとくコメントなんて付けてないが、各メダルの獲得ポイントは2行目〜4行目の変数に格納することにしている。各自「オレオレ配点を決めたい」というなら、ここをいじればよろしかろう。このソースを各ブラウザの開発者ツールのデバッグコンソールにコピペすれば並び替えしてくれる。

Bookmarklet

とはいえ、いちいちコピペするのも面倒くさい。そこでBookmarkletを作った。適当にブックマークを作成し、そのURL欄を下記に置き換えればよろしかろう。

javascript:(function(){var r={};r.g=7;r.s=3;r.c=1;$.fn.f=$.fn.find;$.fn.t=$.fn.text;r.t=$(".tableForm").f("tbody");$.ajax({url:"http://london.yahoo.co.jp/medal/?page=2",async:false,success:function(a,b){var c=$(a).f(".tableForm").f("tbody tr");r.t.append(c)}});$.ajax({url:"http://london.yahoo.co.jp/medal/?page=3",async:false,success:function(a,b){var c=$(a).f(".tableForm").f("tbody tr");r.t.append(c)}});r.$l=r.t.f("tr");r.$l.each(function(){var g=$(this).f(".tdMedalCountGold").t();var s=$(this).f(".tdMedalCountSilver").t();var c=$(this).f(".tdMedalCountCopper").t();$(this).data("p",g*r.g+s*r.s+c*r.c);$(this).data("r",$(this).f(".rank").t())});r.$l.sort(function(a,b){var c=$(b).data("p")-$(a).data("p");if(c==0){c=$(a).data("r")-$(b).data("r")}return c});r.$l.removeClass("even");r.$l.filter("*:even").addClass("even");r.$l.f(".tdRank p").removeClass("rank01 rank02 rank03 rankUnder");r.r=1;r.p=0;r.k=0;r.$l.each(function(){var a=$(this);var b=a.f(".rank");var c=0;if(a.data("p")===r.p){b.html(r.k+"<small>("+b.t()+")</small>");c=r.k}else{b.html(r.r+"<small>("+b.t()+")</small>");r.k=r.r;c=r.r}r.r+=1;r.p=a.data("p");if(c<4){b.addClass("rank0"+c)}else{b.addClass("rankUnder")}});r.t.html(r.$l);})();