過去記事更新

2015年3月31日火曜日

JavaScript「日替わりランダムリンク」を作ってみたよ!!!!

DAILY RANDOM LINK with Java Script

 ローペースながら記事数も増えてきたので、以前から考えていた「日替わりランダムリンク」を作ってみました。
 その名の通り用意したURL集団の中から、毎日ランダムな1つを選ぶというものです。
  →探してみると、意外にそういう機能が見つからない。

(しばらくトップページに設置していましたが、あまり動きが無かったので現在は外しています)

 簡単に実装できるかと思いきやいろいろと難関があったりもしましたが、これについて詳しく書いていきます。
 一応Bloggerのユーザ向けですが、特に難しいことはしていないためJava Scriptさえ使えれば他のブログ等でも問題なく使えるはずです。
  ※勝手に使っていいよ!



*もくじ:
▼特徴~概要(↓)

▼実装例@Blogger ▼実装例@汎用

▼検証(予測)スクリプト

▼原理



Daily Random Link (Java Script)


◇特徴
(機能面)
・毎日飛び先が変わる
・その日のうちは同じ飛び先になる
・飛び先は任意の個数を指定できる(十でも千でもいい)
・後述の検証スクリプトにより1年分の結果を予測可能

(実装面)
・ブラウザ上で「ショートカットをコピー」してもどこに飛ぶかは分からない(ようにした)
・スクリプトが無効の環境でも特定ページに飛ばせる
・遷移方法をテキストリンクやボタンにもできる(当然か)
・「日替わりランダム画像」(→クリックで特定記事へリンク)とかも可能


◇概要
・日付から一意の鍵数字を生成
・振幅1の鋸歯状波により0~1レンジに変換
・対象総数で増幅し、自然数部を配列番号に使用
(わざと小難しくしてみた)



◇実装例@Blogger →汎用版▼下のほうで。
 ソースはこちら(だいたい調整済)。
<b:if cond='data:blog.url == data:blog.homepageUrl'>

 <div style='border:1px solid magenta;line-height:1.3em;width:80%;padding:5px 5px 5px 1em;margin-left:2em;'>

 <form>
   <input onclick='MOVE()' type='button' value='DAILY RANDOM LINK'/>
  </form>

 <A onclick='MOVE(); return false;'
   expr:href='data:blog.homepageUrl'
   style='font-size:x-large; color:magenta;'>■日替わりランダムリンクだよ!</A>(Daily Random Link)

 </div>


 <script type='text/javascript'>
  URL_GROUP = [
   '15/01/sample1','15/02/sample2','15/03/sample3','15/04/sample4',
   '11/01/sample5','12/02/sample6','13/03/sample7','14/04/sample8'
  ];

  date = new Date();
  key = ( Math.sqrt(date.getMonth()+0.1) + Math.sqrt(date.getDate()-0.1) ) * 1.94;
  DAILY_TARGET = Math.floor( key % 1 * URL_GROUP.length );
  URL = '<data:blog.canonicalHomepageUrl/>20' + URL_GROUP[DAILY_TARGET] + '.html';

  function MOVE(event){
   location.href = URL;
  }

 </script>

</b:if>

◆HTMLテストサンプル - 20150412re6@Googleドライブ
(上にある矢印からダウンロードできます)

 実行イメージはこんな感じ。
 とりあえずフォーム(リンクボタン)とテキストリンクを用意したので、好みで適当にいじってください。


*Blogger構文について
 ソースのうち、この色の文字Blogger専用の独自構文です。
 なんとなく分かると思いますが、この場合はトップページにだけ表示する<b:if>で囲っています(サンプルファイルでは外しています)。

 ブラウザで上記サンプルを表示してリンクをクリックしてみると、だいたい以下のようなページに飛ぶはずです。
file:///~~^/<data:blog.canonicalHomepageUrl/>2015/01/sample1.html

 これは、URL_GROUPから選ばれたものに対して、先頭に固有のブログアドレス + 数字で「20」、末尾に「.html」が足されたものです。
 ちなみにBloggerの記事URLは「ブログごとのアドレス/年/月/記事ごとのアドレス.html」という形式です。
 なんとなくURL集団は1文字でも少ないほうがよかろうと思ったので、上記の例では年の下2ケタ~記事ごとのアドレスを整形するやり方にしています。
 実際にBlogger上のテンプレートに書いた場合は、以下のような完全なURLにリンクされます。
http://(自分のブログID).blogspot.com/2015/01/sample1.html

 ちなみにマウスでリンクをポイントした時点では、ブラウザのステータスバーに表示されるリンク先がトップページになっているというのがささやかなドヤポイントです。



 当初は基本動作を抑止するというpreventDefault()というのを使っていましたが、どうも環境によって(?)うまく動作しなかったので外しました。
 とりあえず return false; と書いてあれば、デフォルトのhref(スクリプト無効環境用)を無視しつつの onclick=安定しているようなのでこちらのほうがいいかなと。
  ※動けばいいんだよ!


(追記)
 当初、飛び先URLとして<data:blog.homepageUrl/>を使っていましたが、これだとURL末尾に?m=1が付くモバイル版で問題になることが分かったため、正規URL(canonical URL)である<data:blog.canonicalHomepageUrl/>に修正しました。
  →参考:http://www.kuribo.info/2012/03/blogger-blogspotjp.html
 ついでに、モバイル版ではウィジェットにmobile='yes'を付けておかないと表示されないことを忘れていましたが、現在は表示されるはずです(手元で確認済)。
 まったく同じミスをFC2拍手(解説は未投稿)の実装時にもやっていたので、まったく成長していない……と思いきや気づいてすぐ修正できたのでよかったなと。

 
◇実装例@汎用
 上記の例から、Blogger構文や装飾を排除して簡略化したものがこちら。
 ブログ等のJavaスクリプトが動く箇所に貼り付けるだけで動作します。
Bloggerなら「レイアウト」のHTML/JavaScriptガジェットとか)

 あとはこの色の部分を自分用に書き換えてください。
<div>

  <form><input onclick='MOVE()' type='button' value='DAILY RANDOM LINK'/></form>

 <a href='http://--Default-Link--.com/' onclick='MOVE(); return false;'>■日替わりランダムリンクだよ!</a>

 <IMG id='DRL_IMG'
   src='http://4.bp.blogspot.com/-MeiJs2E4zT8/UuPBh0v3hLI/AAAAAAAAFG0/t9QQ1FfrhKw/s220/20131026-thum.jpg' />

</div>


<script type='text/javascript'>
 URL_GROUP = [
//(例)
//  'http://--Your-Blog-Address--/sample1.html',
//  'http://--Your-Blog-Address--/sample2.html',
//  'http://--Your-Blog-Address--/sample3.html',

  'http://1.bp.blogspot.com/-OypScDoi0R4/VRlmO_aHnNI/AAAAAAAAIac/JTXnOaX9IOk/s300/20150331%2BDaily%2BRandom%2BLink.jpg',
  'http://3.bp.blogspot.com/-fwa9nMLTYJo/U76831ulWQI/AAAAAAAAGLo/O5oY0OHZmPk/s300/20140711_PS3(80G)cleaning_a9a_Done.jpg',
  'http://2.bp.blogspot.com/-n17Z_GnK5bU/TqrX4WI4XII/AAAAAAAAARo/7BdsZIdqBPU/s300/DARK_SOULS_Platinum.jpg'

 ];

 date = new Date();
 key = ( Math.sqrt(date.getMonth()+0.1) + Math.sqrt(date.getDate()-0.1) ) * 1.94;
 DAILY_TARGET = Math.floor( key % 1 * URL_GROUP.length );
 URL = URL_GROUP[DAILY_TARGET];

 function MOVE(event){
  location.href = URL;
 }

 document.getElementById('DRL_IMG').setAttribute('src', URL);

</script>

◆HTMLテストサンプル(↑を貼り付けてあるだけ)

 ここでは、別パターンとして日替わりランダム画像も表示させています。
 この例では、IMGタグの中で……
  http://~~/20131026-thum.jpg
 ……の画像を指定していますが、実際にはURL_GROUP内のいずれかが表示されます。
 なおページ読み込み中は最初に指定した画像がわずかに表示されるため、最初からsrcを指定せずに <IMG id='DRL_IMG' /> と書いてしまっても構いません。
 一応、src= に指定してあるほうはスクリプトが動かない環境で表示するためのデフォ画像という意味合いがありますが、まああっても無くても動くので好みで変えてみてください。

 また、この例では対象URLはベタ打ちにしています。
 URLに共通部分が少ないとか、外部リンク等で飛び先アドレスが不統一のケースではこうなるかなと。
 その他の細かい最適化については、Blogger向けの例のように各ブログ等の独自タグも使うといいでしょう。

 なおBloggerであっても、レイアウト>ガジェットを追加>HTML/JavaScriptのスペースでは独自タグが動作しない(っぽい)ので、上記の簡略化版のような書き方にする必要があります。
 テンプレートの編集であれば独自タグを使ってより柔軟に記述できますが、不用意に書き換えると最悪、ブログが再起不能になりかねないので注意してください。
  ※無理しないほうがいいよ!




verifying script


◇検証スクリプト
 「日替わり」ということは、つまり1日ごとに結果が変わるということなので実装後のテストは困難です。
 そこで、予めどの日付に何が出るかを予測する検証スクリプトを作っておきました。

 ダウンロードはこちらから。
◇Daily Random Link Checker
https://drive.google.com/file/d/0B2cIp72eMMBUVmxaczFvNlYxd3c/view?usp=sharing
(リンク共有@Googleドライブ)

 とりあえず、これをそのままブラウザで開くと1/1~12/31の結果が表示されます。
  →2/30とか6/31とかあるけど気にしてはいけない。
 動作イメージはこんな感じ。


 動作が確認できたら、上記ファイルをテキストエディタ等で開いて適宜書き換えてみてください。

*要点
・デフォルトではtest0.html ~ test499.html500件が対象(訂正)
・日付(左列)は1月1日=101、12月31日=1231(MMDD)で表示
・表の一番上にはきょうの分が表示される
IDが内部的な番号で、URLが実際の飛び先
・Excel等にテキスト形式で貼り付けて使用可能


*書き換えポイント
MAX=500;の数字を変えると最大数が変わる
i=0;の後のfor文が計算部分
document.write以降が表示部分
良かれと思って月初に見出しを入れているけど、ジャマなら// 月毎整形の後のdocument.writeを消すかコメントアウトすればOK
 *
参考用として正弦波版もあり(コメントアウトされています)
key=Math.floor あたりが核心部なので、いじってみるのも一興


 よく分からないという方は、とりあえず15行目 // 実際の入力例付近にある/**/を消して保存し、ブラウザを再読み込みしてみてください。
 すると結果がtest1.htmltest8.htmlのみになるはずです。
 あとは、サンプルを参考にtest1.htmlの部分を書き換えるだけです。

 一応基本だけ書いておきます。
URL_GROUP = [ ]; の内側に対象のアドレスを入れる
・半角スペース(全角はだめ)、タブ、改行は自由に入れていい
・アドレスの両端は半角の ' (シングルクォート)または " (ダブルクォート)で囲む
・囲んだ文字は , (カンマ)で区切る
"……","……","……"と繋げていった最後はカンマを入れなくていい
 →ただ、最後にカンマがあってもエラーにはならない(?)みたい。
・正しく書き換えて保存してからブラウザで見ると「対象URL ◆件」の部分に総数が出る

 ここでうまく行っているようなら、書き換えた対象URLをそのまま上記スクリプトにコピーし、ローカルやサイト上でも同じ結果になっているか確認してみてください。
 なお注意点として、こういったスクリプトは構文エラーを察知しづらいため、ほんのちょっとしたミスでスクリプト全体がまったく動かなくなることが多々あります。
 このため正常動作しているバックアップを用意したり、わずかでも書き換えたらすぐ動作確認するのがコツです。



 とりあえず今のところ、ローカルでの予測結果と◆トップページからのリンク先が一☆致していたのでまあ信用できるかなと思います。
 ただ、実際の運用では定期的に対象URLを追加していく(というか、したくなる)かと思います。
 対象の総数が変われば予測結果も激変するので、その際にも参考になるはず。


(余談)
 この検証スクリプトの真の目的は、結果予測そのものではなく精度の検証です。
 現行のものになるまでいろいろ試しましたが、当初はひどいものでした。
 初期段階ではkeyの部分に101~1231をそのまま使用していたのですが、これだと1月と11月、2月と12月の結果がまったく同じだとか、3ヶ月くらいの周期で同じ内容が繰り返されているというケースがありました。

 これは、ブラウザ上で任意の対象URLをCtrl+Fでページ内検索し、Enterを連打してみると分かります(伝わるかな)。
 興味のある方は、for文の中にあるsin版のコメントを外し、その前の行に key=days[i]; を入れてみてください。
 一見そこそこ数字がバラけていますが、よく見ると1/1~1/21と8/11~8/31の結果が完全に一致しているのが分かるはずです。
 これではよろしくないぞということで振幅だけではなく横軸の取り方も複雑にすることで対応してみたのですが、そのあたりは次項で。

 そもそも365日程度の想定な上に定期的な対象URL追加がある(だろう)ことも考えると超高精度な乱数は必要ないのですが、たとえば鍵数字の生成に「年」も加える等でいろいろアレンジしてみるのも面白いかと思います。



◇原理/解説
 簡潔に言うと、このスクリプトは「日付ごとに異なる鍵数字でグループから呼び出す」ものです。
 ソースで言うとkeyがキーとなる鍵数字で、これを元に選ばれたものがDAILY_TARGETになります。
 さほどJava Scriptにも詳しくないしおそらく後で自分が見返しても分からないということもあるため、動作原理について詳しく書いておきます。
 ちょっと横道(余談)もありますが、そのあたりも参考までに。


*自力でランダム関数を作る
 最初にネットを色々見て回ったところ、Java Scriptによる簡易的なランダムリンクは、ほとんどでMath.random()関数が使われていました。
 これはランダムな0~1(正確には1に非常に近い1未満まで)の数字を返す非常に高精度なもので、これに対象URLの総数を掛ける(+端数を切り捨てる)ことで簡単にランダムリンクを作ることができます。
 ただし、この関数では実行ごとにランダムな値を返すため、「日替わり」という用途には向かないことが分かりました。
 まあクリックごとにランダムに飛ばす……というのもシンプルでいいんですがそれだとなんか飽きそうというのと、ぶっちゃけて言うと「日替わり」のほうが集客が望めそう……かなあ、という狙いがありました。


 そんなわけで、どうやら一意な日付キーを0~1の範囲に変換すればよさそうだと考えて、最初に思いついたのが正弦波(サイン波)でした。
 y = sin(x)は波状の曲線なので、にどんな数字を入れようが必ず-1~1を返します。
 この式について振幅を半分にして半分ずらすと、0~1の範囲になるため扱いやすくなります。
  →波の上から下までのタテの長さが振幅。
 これを数式で書くと y = 0.5 * sin(x) + 0.5 となります。

 実際には0~1ではなく0~対象URLの総数にしたいので一般化する必要があります。
 といっても簡単で、ずらす長さを変えればいいだけです。
 「対象URLの総数」lengthとすると、求める式は
   y = length / 2 * sin(x) + length / 2
 となります。
 対象総数が100であれば、 y = 50 * sin(x) + 50 です。


 これをにするとこんな感じ。
正弦波(サイン波)@日替わりランダムリンク

 横軸がx(日付ごとに異なるキー)で、それぞれにy(0~100の数字)が選ばれていることが分かります。
 なおMath.floorというのは端数を切り捨てて整数にする関数です。

 上記の検証スクリプトではコメントアウトされていますが、ここまでの流れをスクリプトで書くとこうなります。
DAILY_TARGET[i] = Math.floor( Math.sin(key) * URL_GROUP.length/2 + URL_GROUP.length/2 );


 この時点でよーしできたーと思いましたが、いろいろ検証していくと精度的な問題が見えてきました。
 これは正弦波が波型であるためで、どうしても波の頂上部と底部に出力が集まりやすいのです。
 上記の例で言えば0付近、100付近は非常に出やすいものの50付近が極端に少ないという結果が見られました。
 これは根本的な問題で、二乗してみたりの取り方を工夫してみたりしても解消することができませんでした。

 そこで思い出したのが、 はるか昔に 学校で習った三角波でした。


*ノコギリ波を応用する
 三角波というのは、文字通り三角形のような波形を描くもので、曲線である正弦波を直線にしたようなものです。
 代表的なものは /\/\/\/ ……というのようなジグザグ直線ですね。

 今回のスクリプトは、より使い勝手が良さそうなのこぎり波を採用しました。
  ※三角波の一種だよ!
 これは /|/|/|/ ……という形の直線で、その名の通りのこぎりのような形です。
  →◆wikipedia「のこぎり波」
 この波形はムラが無いため、さえうまく取れば結果が偏りにくくなることが期待できます。

 なお、こういった三角波を通常の数式で表すのはとんでもなく難しい(収束する近似式で表される)のですが、実際には「~~が偶数の場合は~~、奇数なら~~」のような条件文を許容すればかなり簡単に記述できます。
 具体的には、振幅1、周期1ののこぎり波「 y は x を 1 で割った余り」と表現できます。
 これはプログラム的に y = x % 1 と書くだけで実現できます。
 この式もやはりにどんな数字を入れようが0~約1の範囲になります。

 あとは、先ほどの正弦波と同じように振幅に総数を掛けるだけです。
 これをスクリプトで書いているのがこの部分。
DAILY_TARGET = Math.floor( key % 1 * URL_GROUP.length );


 この増幅のこぎり波を実際に計算し、結果をグラフ化したのがこちら。
鋸歯状波(のこぎり波)@日替わりランダムリンク

 この例では(横軸)を1~100としてテストしています。
 パッと見ても正弦波に比べて偏りが無くなっているのが分かるかなと。

 ここでに当たる部分が i * 0.194 となっているのは周期調整を行っているためです。
 見た目で言うとのこぎりの刃1つごとの長さ(横幅)を変えています。
 実際に y = x % 1 のままで0~100を代入すれば必ず1で割りきれる=すべてゼロになるだけなので、微妙に余りが出るようにずらす必要があるわけです。

 「1」からズレてさえいれば1.1でも0.3でも大丈夫ですが194なのはまあ落款みたいなものです。
 ここは好きな数字に変えても問題ありませんが、当然0.5のようなキリの良い数字、百以上のような大きすぎる数字なんかではあまり意味がありません。
 もっと言えば、小数点以下を2ケタ(かそれ以上)程度にしたほうが精度が期待できるかなと思います。
 なお最終的な採用案は1.94としていますが、これは下記の横軸対策も考慮して決定したものです。


*日付キーを複雑かつ狭い範囲に取る
 いろいろやって残った問題は、ある周期で似た結果になりがちという問題でした。
 これは▼検証スクリプトにも書いた通りで、実際に1年間の結果を出してみて初めて分かったことでした。

 原因としては、日付キーの元になる数字がMMDDの3~4ケタだったためです。
 この場合、日付ごとに101(1/1)から1231(12/31)となり十倍以上の差がありました。
 せっかくのこぎりの傾きを0.01みたいな繊細さで調整しているのに、そこに100~1000のような大きすぎる数字を掛けあわせれば台無しになるのも当然だったわけです。
 そんなわけで、M(月)、D(日)という2つの数字からできるだけ複雑で、かつ短い範囲にまとまるキーを生成するという方向で考えていきました。

 いろいろ試した結果、行き着いたのが「”月+0.1”の平方根と”日-0.1”の平方根の和」です。
  ※自信作だよ!
 スクリプトではこの部分です。
key = ( Math.sqrt( date.getMonth()+0.1)
+ Math.sqrt( date.getDate()-0.1) ) * 1.94;


 まず適当に数字を小さくするために平方根を使いつつ、1、4、9で自然数にならないように0.1だけずらしています。
 ついでに11月1日と1月11日のようなパターンも考慮して月はプラス、日はマイナスとしました。
 ちなみに、Java ScriptDate変数において月は0~11(日は1~31)なので、意図的に月はマイナスではなくプラスのほうにしています。



 以上のように振幅/周期/キーを設定した結果、今のところ満足できる精度になったかなと思います。
 試しに使って頂くか、また何かを作る際の参考になれば幸いです。


(余談)

 今回のことでいろいろ思い出したのですが、学生時代にはラプラス変換だのフーリエ級数だのを使って電子回路の過渡現象を解析するとかやっていました。
 もはやあんなのは名前だけふんわり覚えている程度なのでどんなに初歩の問題も解けないどころか、今では中学生程度の因数分解すらまともに解けるかあやしいレベルです。
 それでも、波の大きさ(振幅)を変えるのがアンプリチュード・モデュレーション=AM変調で、波の長さ(周期)を変えるのがフレケンシーモデュレーション=FM変調というのがラジオ電波の違いで……みたいな基本的な仕組みくらいは覚えていました。
 そういった経験が、時を経てちょっとだけ役に立ったかなと。

 いわゆる学校で教わることは実生活で役に立たないという話はそれはそれで正しいのですが、これを聞くと自分は「パレートの法則」を連想します。
 何がどこで使えるのかは後にならないと分からないもので……実際のところ大半は無駄ではあるものの、ふとした時に二割程度の役立つ要素に後で気づくことがあります。
 それは学校に限らず、読んだ本や遊んだゲーム、経験したあらゆるものごとについてもおそらく適用可能です。
 ただし、役立つものを役立たせるためには能動的な応用力が必要であって、結局はその人がどこで何を使おうとするかに尽きるのだと思います。

 そして自分は趣味にも意味や役割を持たせたいと思うことがあり、このサイトをその基点にしたいと常々思っています。
 長く続けて、いつかああ、あの時のアレがここで役に立ったなと思えればしめたものです。
 そんなことを思った鋸歯状波を応用した日替わりランダムリンクでした。

 今回は以上です。


マンガでわかる電気数学

マンガでわかるフーリエ解析

0 件のコメント:

コメントを投稿