文字列反転日本語対応

先日の文字列をインプレイスで反転させるコードは、ASCII文字専用でした。
軽量言語では str.reverse() みたいな感じで多バイト文字でも簡単に反転できますが、C/C++ でやるとなると、かなり難しいです(私にとっては)。

まず、文字コードの判定から始まって、1バイト文字と多バイト文字の混在にも配慮しないといけないし、Shift_JISなどは2バイト目に’5C’が来るいわゆるダメ文字もあったりして、非常に厄介です。

まあ、しかし頭の体操には丁度よいのではないかということで、ぼちぼち書いてみたのが以下のコードです。

#include <stdlib.h>

char * revStr( char * str ){
	int len = strlen( str );
	char *buf;
	buf = (char *)malloc(sizeof(str));
	int j = 0;

	for( int i = len-1; i >= 0; i-- ){
		if (str[i]>=(char)0x00 && str[i]<(char)0x7F) {
			buf[j] = str[i];
			j += 1;
		}else if(str[i]>=(char)0xC2 && str[i]<=(char)0xDF){
			buf[j] = str[i];
			buf[j+1] = str[i+1];
			j += 2;
		}else if(str[i]>=(char)0xE0 && str[i]<=(char)0xEF){
			buf[j]= str[i];
			buf[j+1] = str[i+1];
			buf[j+2] = str[i+2];
			j += 3;
		}else if(str[i]>=(char)0xF0 && str[i]<=(char)0xF4){
			buf[j]= str[i];
			buf[j+1] = str[i+1];
			buf[j+2] = str[i+2];
			buf[j+3] = str[i+3];
			j += 4;
		}
	}
	buf[j] = '';
	return buf;
}

int main () {
	char *str = revStr("グンミーチャ デー リブラ!");

	printf("%s", str);
	free(str);

	return 0;
}

文字コードはUTF-8に決め打ちです。と言うのも、UTF-8は先頭が0x00…0x7Fだったら1バイト、0xC2…0xDFなら2バイトというふうに確定できるので、扱いやすいのです。そのかわり、仮名・漢字は1文字で3バイト、一部の記号は4バイトだったりします。

インプレイスを維持するのはさすがに無理でした。普通にバッファを確保しています。

次に Objective-C というか、Cocoa フレームワークを使ってみました。若干非効率になってしまってますが、バイト数だのに煩わされなくていいのでラクですね。

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[]) {
	NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
	NSMutableString *str = [NSMutableString stringWithString:@"まにわしらさぎ"];
	NSString *tmp;
	int len = [str length];

	for( int i = 0; i < len/2; i++ ){
		tmp = [str substringWithRange:NSMakeRange(i,1)];
		[str replaceCharactersInRange:NSMakeRange(i,1) withString:[str substringWithRange:NSMakeRange(len-i-1,1)]];
		[str replaceCharactersInRange:NSMakeRange(len-i-1,1) withString:tmp];
	}

	NSLog(@"%@", str);
	[pool drain];

	return 0;
}

π > 3.05 の証明

確認していませんが、聞くところによると、最近の東大の入試で「円周率が 3.05 より大きいことを証明せよ」というのが出たそうです。

なかなか面白い問題です。

pi.gif

私は、上のような図を頭の中に作って、正八角形の中の八つの二等辺三角形の相等しい角が 67.5°なので、円の直径を 1 とすると、円に内接する正八角形の一辺は、

cos( 67.5 ) = 0.383

従って、正八角形の外周は、

0.383 * 8 = 3.064

円周は正八角形の外周より明らかに大きいので、円周率は 3.05 より大きい。

……というふうにやってみたのですが、この方法の泣き所は cos( 67.5 ) を計算しなくてはいけないことですね。

試験場では関数電卓は使えないでしょうし……。

受験生のみなさんはどうやって解いたのでしょうね。上手い方法をご存じの方はぜひ教えて下さい。

移民受け入れよりも先にやるべきこと

少子高齢化が叫ばれています。我が国の人口は、平成19年以降はっきりと減少に転じました。

平成18年 127,770,000
平成19年 127,771,000
平成20年 127,692,000
平成21年 127,510,000
平成22年 127,380,000

これらは各年の10月1日時点での人口の概算です。今年(平成23年)の10月値は(当然)まだ発表されていないので、推測してみましょう。

ある時点の人口を N 、時間を t とすると、次の微分方程式が導かれます。

t で両辺を積分すると、

 (A は積分定数)

従って、

t = 0 とすると logN0 = A なので、

e は自然対数の底です。

さて、平成21年の値が 127,510,000 で、平成22年の値が 127,380,000 なので、定数γは

これを当てはめてると平成23年の10月値は、

およそ 127,324,000

と予測できます。
同様に計算を繰り返すと、平成24年には127,300,000、平成25年には127,290,000 となります。

うーむ、じりじりと減っていきますね……。
なお、この予測では過去一年間の減少が自然減のみで、且つ今後も自然減しか起きないと仮定しています。

国民の頭脳を活用すべし

小学校のころ、先生が「日本は資源のない国だから、頭を使って稼ぐしかない」とよく言っていました。そのときはおっさんの繰り言くらいに考えていたのですが、今にして思えば実に的を射た意見です。

人口減それ自体はまだしも、人口構成が頭でっかちに、つまり年寄りばかりになってしまうのはかなり深刻な事態です。

とは言え、「大変だ、大変だ」と騒いでみても何も始まりません。少しでも出生率を上昇させる努力が必要なのは勿論ですが、もう一つ重要なのは今現在の国民の能力をフル活用することです。

確かに年を取ると考える力は衰えます。しかし、そこをなんとか頑張ってみるのです。60を過ぎても、70を過ぎても、人間考え抜けば何か知恵が出るものではないでしょうか。

何も難しいことを考える必要はないのです。

例えば、そのへんのホームセンターで「ちょうつがい」を買うと、取り付けるための木ねじも一緒に付いてきますよね。これはもともと(100年以上前に)アメリカのスタンレー社が始めたことで、それによって売り上げがぐっと増えたそうです。
「いや、昔はその程度で良かったかも知れないけど、今はあらゆるアイデアが出尽くしてるから無理だよ」とお思いでしょうか。確かにそういう面もありますが、要は心がけ次第です。コロンブスの卵です。

日本には、高い知能と教養を持ちながら、今ひとつ生かし切れていない人が多くいます。これは考えようによっては、膨大な資源が眠っているに等しい状態なのです。

これからは、あらゆる業種の人々が今よりもさらに頭を使って、それぞれの業界で世界をリードするくらいにならなければなりません。

明治政府が富国強兵をスローガンとしたのと同じように、今、私たちは頭脳立国を国是とし、文化として組み込むべきです。

大変困難な道ですが、やってできないことはない筈です。移民の受け入れなどはその後の話です。

参考文献:
総務省統計局 人口推計
Modelling with Differential Equations by David N. Burghes, M. S. Borrie

アイスケーキだとどうなる

明けましておめでとうございます。

ice-cake.jpg
© Gelato Gelato

去年の暮れに書いた、ケーキの切り方と公平の観念の続きです。
あの記事では、「分けるのに時間をかけすぎると、ケーキが劣化してしまう」と書きましたが、頗る観念的で、現実味の乏しい話でした。例え1時間かけたとしても、普通のケーキはそのくらいでは劣化しませんよね。

ですが、アイスケーキだとどうでしょう。
これだと、「交渉にかかる時間」を、無視しがたいコストとして認識できると思います。

このアイスケーキ、n 分後には溶けてしまうものとします。
あなたは、利己的な実業家のA氏とケーキを取り合っています(笑)。じゃんけんで勝った方がまず希望する取り分を提示し、相手が承諾すればその通りに分け、拒否すれば、今度は相手が取り分を提示します。1回の交渉には1分かかるものとします。提示できる取り分は n を超えない整数分の 1 です。両者が自分の取り分が最も多くなるように行動したとすると、

まず、n = 1 の場合、
先手が、取り分 1 (全部)を提示し、後手が拒否して終わるでしょう。 アイスケーキは溶けて無くなってしまいます。この場合、交渉のコストは100%で、両者の取り分はゼロになってしまいます。(まぁ、しかし、1分で溶けるケーキというのも無理がありますね)

n = 2 の場合、
先手が仮に 1 を提示すると、後手は必ず拒否するので、2回目の提示で今度は後手から 1 / 2 を提示され、この場合、拒否しようと応諾しようと先手の取り分はゼロになってしまいますから、1 を提示するのは誤りです。結局、先手は最初から 1 / 2 を提示し、後手が応諾して、仲良く半分こすることになります。

n = 3 の場合、
先手が仮に 1 を提示すると、後手は必ず拒否し、2回目の提示で後手から 1 / 3 を提示されることになるでしょう。なぜなら、1回目の提示でケーキはすでに 2 / 3 に減っており、残り2分、すなわち交渉回数2回ということは、n = 2 の場合と同じ事になるので、( 2 / 3 ) * ( 1 / 2 ) = 1 / 3 を提示せざるを得ないからです。
結局、先手はこれを先読みして、最初から 2 / 3 を提示し、後手が応諾することになります。

n = 4 以上の場合も同様に、先手が仮に 1 を提示すると n – 1 の場合と同じになることから、両者が十分に賢ければ、n が偶数の時は文字通り半分こ、奇数の時は、先手が ( n + 1 ) / ( 2 * n ) を提示し、後手が応じる( 取り分は ( n – 1 ) / ( 2 * n ) )ことで決着します。

n が奇数の時は、先手が 1 / n だけ得することになりますが、n が十分に大きければ、結局、正解は、「最初から半分こ」に限りなく近くなる、というわけですね。

 

参考文献: Avinash K. Dixit, Barry J. Nalebuf
The Art of Strategy: A Game Theorist’s Guide to Success in Business and Life

ケーキの切り方と公平の観念

またも頭の体操です。

ここにケーキがあります。まん丸い奴です。
二人の兄弟でこのケーキを分けるのですが、もっとも公平な分け方はどのようなものでしょう?

cake.jpg

手続きの正当性

多くの人が真っ先に考えつくのは次のような方法だと思います。

まず、じゃんけんでも何でも良いので、「切る人」と「分ける人」を決めます。例えば、兄が切ったら、弟が好きな方を取って良いわけです。
切り方が不揃いだと、大きい方を弟に取られてしまいますから、兄はできるだけ同じ大きさになるように努めることでしょう。

ほぼ同じ大きさの二切れのうち、弟が小さい方を選んだとしても、それは本人の判断ですから問題ありません。

この方法は公平さを担保する手続き、いわゆるデュー・プロセスを志向していると言えます。

権威に依る

別の考え方もあります。
上記の方法では、兄弟の取り分がほぼ同量になることが予測されますが、果たしてこれで本当に公平と言えるのでしょうか?

例えば、弟が小学校一年生、兄が六年生だとします。半分こでは、体の大きさに比して、弟には多すぎ、兄には少なすぎるかもしれません。

あるいは、年の離れた兄弟で、弟は小学校六年生、兄は二十五歳だとどうでしょう?
甘いものが食べたい盛りの弟に多く与える方が公平かもしれません。

下手に手続きを固定化するよりは、一定の権威を持つ裁定者に従う(例えばお母さんに切ってもらう)、と定めた方が合理的であるという考え方もあります。(でも、二十五歳の兄が「お母さん、切って~」とか言ってたら、マザコンっぽくて嫌ですね(笑))

長引くと価値が目減りする

イソップ物語に、小熊の兄弟がパンの分け方で争う話があります。狐が割って入って、大きい方のパンを少しかじる、っていうあの話です。
結局、「まだ、こっちの方が大きい」「今度はこっちが大きくなった」と言って小熊が争いを続けているうちに、大半が狐に食べられてしまうわけですが、人間界にもこの狐とそっくりなのがいます。そうです、弁護士ですね(笑)

この寓話は、紛争が長引けば弁護士料を初めとしたコストが掛かる、という教訓を与えてくれます。

仮に、悪い狐に上前をはねられることが無かったとしても、時間が経てば、パンやケーキは幾分劣化し、最後には食べられなくなってしまいます。

いろいろな考え方がある

私を含めて、自分では賢いつもりの石頭に限って、最初に挙げた、一方が切って他方が好きな方を取るという方法を思いついて、そこで思考停止してしまいがちです。
実際には、単に大きさが同じならば公平というわけではないし、切り分ける際のコストも考慮に入れなければいけません。
中には、「大きさにはこだわらないが、いちごだけは譲れない」という人もいます(女性に多い)。上に乗っている砂糖菓子が好きな人もいます。

現実の社会にはこのような主観的要素が多々あり、定量的に把握できる要素、例えばケーキの体積や重量しか考慮に入れずにぶった切るのは、実は公平とはほど遠いのです。

麻雀とサンクトペテルブルグのパラドックスと小泉進次郎

よく漫画やドラマなどで怖い人が高レート麻雀というのをやっています。千点一万円とか、そういうやつです。
しかし、究極の麻雀と言えばいわゆる青天井ということになりましょう。

Yahoo知恵袋で、kimagurel964さんという方が、考え得る最高の飜数は50であると回答されています。

すなわち、

    

で、リーチ(1)、一発(1)、トイトイ(2)、三暗刻(2)、三槓子(2)、混一(2)、混老頭(2)、小三元(2)、白(1)、中(1)、ダブ東(2)、ドラ(32)です。

東場東家のロン和了りで、ドラは、三つ目の槓裏に至るまで全て4枚ずつのっていると仮定した場合です。
符は副底(20)、么九の槓子(32)が三つ、同じく么九の明刻子(4)が一つ、雀頭が役牌(2)、門前ロン(10)なので、合計132、切り上げて140です。

さて、これって青天井だと何点になるのでしょうか。

麻雀の点数は一般に

符 * 2 (飜数+2) * X

です。( X は親の場合 6、子の場合 4 )
つまり、

140 * 2 ^ ( 50 + 2 ) * 6
= 3.78302368699121660000E+018

ですね。……って、よく分かりませんね(笑)

千点百円とすると約37京8302兆円と言えば、少しは分かりやすいでしょうか。

麻雀の青天井はこのように実際には有限ですが、期待値が無限大となってしまうゲームの考察として有名なのが「サンクトペテルブルグのパラドックス」です。
次のようなゲームを考えてみます。

コインを投げて一投目で表が出たら二万円もらえます。
一投目で裏が出て、二投目で表がでたら四万円、
一、二投目で裏が出て、三投目で初めて表が出たら八万円。

つまり、n 投目に初めて表が出たら 2 n 万円もらえるというゲームです。

ただし、このゲームには参加料がかかります。
参加料をいくらまでなら払っても引き合うでしょうか?

n – 1 回まで裏が出る確率が ( 1 / 2 ) n – 1
n 回目に表が出る確率が 1 / 2

ですから、n 回目に初めて表が出る確率は、

( 1 / 2 ) n – 1 ( 1 / 2 ) = ( 1 / 2 ) n

です。このとき得られるリターンは 2 n

従って期待値は、

ですね。

あー、なるほど、期待値は無限大、これは百億円払っても参加する価値ありますね!

……って、なにかおかしいですよね。
これがサンクトペテルブルグのパラドックスです。

サンクトペテルブルグのパラドックスを解決する方法の一つとして提案されているのが、期待効用理論です。

ミクロ経済では限界効用という考え方があります。
ビールを飲むとき、一杯目はすごく美味しい。
二杯目はまぁまぁ美味しい。
三杯目はもういいかな……
という風に、量が増えると感じる美味しさ(限界効用)は減っていきます。これを、限界効用逓減と言います。

上のゲームも同じことで、100億円もらえる嬉しさは10億円もらえる嬉しさの10倍かというとそうでもなく、「多すぎてよーわからん」という状態になるはずです。

従って、金額の期待値が無限大になるからといって、期待効用も無限大になるわけではなく、あるところで頭打ちになるでしょう、というわけです。まぁ、その頭打ちがどのへんで来るのかは人によって違うかも知れませんけどね。

話は、先頃の参議院選挙に飛びます。
この選挙で、自民党の応援演説に各地で引っ張りだこになったのが、小泉進次郎氏です。

小泉氏は、民主党が掲げる「最小不幸社会を目指す」とのスローガンを批判し、「我々は最大幸福を目指す」と述べました。

最大幸福というのはベンサムの古典的な理論で、いわゆる「最大多数の最大幸福」というやつです。

例えば、ここに100人の「飲んべえ」が住む村があるとします。どんな村でしょう(笑
ジョッキ100杯ぶんのビールがあるのですが、強欲な村長が50杯を独り占めし、残りの99人は一人約0.5杯ぶんしか飲めません。
飲んべえがビールを飲んだときの幸福度は、およそ自然対数に比例するとしましょう。
村長がいくら飲んべえだと言っても、50杯も飲めませんから、この人の幸福度はせいぜい、
loge( 1 + 50 ) = 3.932 です。
他の人の幸福度は
loge( 1 + 0.5 ) = 0.405 です。
この村の幸福度の総和は、

3.932 + 0.405 * 99 = 44.027

です。
もし、全員で仲良くジョッキ一杯ずつ飲めば、

loge( 1 + 1 ) * 100 = 69.315

だったはずなのに。

これが、最大多数の最大幸福です。
小泉進次郎氏はこのことを言っているわけですね。

もっとも、民主党の「最小不幸社会」も実はそれなりに合理性のある考え方です。
上記の例えで言えば、ビールを0.5杯どころか一滴も飲めない、ゼロの人が出てこないようにするという考え方ですね。大多数が幸福でもゼロの人が居たらダメじゃないか、と。

私は民主党は大嫌いですが、この考え方は一理あります。

ただ、スローガンとして見たときに「最小不幸」というのはまずい。言葉選びが実に拙劣であると言わざるを得ません。不幸という字面でもう気が滅入ってきます。

その点を鋭く見抜いて批判を展開した小泉氏は、若いのに大変な炯眼ですね。

谷垣さんもまずまずの人物ではありますが、野党の党首としては少し印象が薄いように思われます。

自民党はぜひ、小泉さんを総裁にするべきです。無茶なようでも、その位やらないと政権奪還は覚束ないのではないでしょうか。

清水の舞台からまた飛び降りる

また、清水の舞台から飛び降りてみようかと思います。

と言っても、外車でも買って散財しようかという話ではありません。ちょっとした頭の体操です。

以前、清水の舞台から飛び降りたときの致死率は6割程度であり、逆に言えば4割は助かるというようなことを書きました。

そして、二度続けて飛び降りてなお生きている確率は16%だ、というようなことも書いたのですが、あまりにも粗雑な理論でした。
いや、そもそも致死率60%ということ自体、漫画で仕入れた知識なのですが(^^;

で、どう粗雑なのかというと、各飛び降り(というのも変な言い方ですが)は独立試行ではなく、前回の結果の影響を受ける、いわゆるマルコフ試行なわけです。
つまり、飛び降りて死ななかったとしても、怪我は免れないはずであり、後遺症に苦しむ場合が少なくないはずなのに、そのことが考慮に入れてありません。

そこで、飛び降りても生きていた場合をさらに、無傷・または軽症の場合(A)と、重傷により後遺症が残った場合(Aw…wounded)に分けてみましょう。

生き残った場合の半分が後遺症を伴い、後遺症のある状態で飛び降りると、受け身の体勢をとるのが困難なので生存率が20%に低下してしまう、ということにします。

樹形図を書くとこんな感じです。

二度飛び降りてなお生きている確率は、

0.2 * 0.2 + 0.2 * 0.2 + 0.2 * 0.2
=0.12

12%ですね。
樹形図は省略しますが、三度飛び降りてなお生きている確率は、3.2%です。

うーむ、かなり楽観的な設定にもかかわらず、生存率は低いですね、やっぱり。
くわばら、くわばら~

清水の舞台から飛び降りる

今日のNHKのクローズアップ現代で、京都の清水寺に、日本画家の中島潔さんが46枚のふすま絵を奉納したことが特集されていました。鰯の大群の絵です。テレビで見ただけでも凄い迫力でしたが、いつかこの目で見てみたいものです。


©New Japan: CC BY-SA

ところで、清水寺と言えば清水の舞台、清水の舞台と言えば、「~から飛び降りる」という成句が頭に浮かびます。
聞くところによると、清水の舞台から飛び降りた場合の致死率は60%程度だそうです。4割近くは助かる、という計算になりますが、思ったより助かる確率高いですね。もっとも、助かると言っても怪我は免れないでしょうが……。

では2回続けて飛び降りてなお生きている確率はどのくらいでしょうか。

( 2 / 5 ) ^ 2 = 0.160

16%ですね。
では、3回だとどうなるでしょう。

( 2 / 5 ) ^ 3 = 0.064

生存率は約6%と劇的に低くなってしまいます。

やっぱり、清水の舞台から飛び降りるのは一生に一度にしておいたほうが良さそうです。

 

應接間グラジオラスの色赤し

今後100年の春分の日と秋分の日

もうすぐ春分の日ですね。

ご存じの通り、春分の日及び秋分の日は天文学的計算によって求まるので、年によって違います。

というわけで、春分・秋分の日を求める関数を考えてみます。
もっとも、理科年表を見てプログラムに置き換えるだけです。実は以前別のところで全く同じ事をやっています。今回はRubyを使ってみました。

def equinox( year, spring ) 
	((spring ? 20.8431 : 23.2488)+0.242194*(year-1980)-
	(year-1980)/4).floor
end

table = "<table><tr><th>年</th><th>春分の日</th><th>秋分の日</th></tr>"
(2010..2109).each do |y| 
	table +=
		"<tr class='"+(y%2==0?"even":"odd")+"'><td>"+y.to_s+"</td><td>3月"+
		equinox( y, true ).to_s+"日</td>""<td>9月"+
		equinox( y, false ).to_s+"日</td></tr>"
end

p table + "</table>"

Rubyは範囲オブジェクト(2010..2109)が使えるので便利ですね。
結果は以下の通りです。

春分の日 秋分の日
2010 3月21日 9月23日
2011 3月21日 9月23日
2012 3月20日 9月22日
2013 3月20日 9月23日
2014 3月21日 9月23日
2015 3月21日 9月23日
2016 3月20日 9月22日
2017 3月20日 9月23日
2018 3月21日 9月23日
2019 3月21日 9月23日
2020 3月20日 9月22日
2021 3月20日 9月23日
2022 3月21日 9月23日
2023 3月21日 9月23日
2024 3月20日 9月22日
2025 3月20日 9月23日
2026 3月20日 9月23日
2027 3月21日 9月23日
2028 3月20日 9月22日
2029 3月20日 9月23日
2030 3月20日 9月23日
2031 3月21日 9月23日
2032 3月20日 9月22日
2033 3月20日 9月23日
2034 3月20日 9月23日
2035 3月21日 9月23日
2036 3月20日 9月22日
2037 3月20日 9月23日
2038 3月20日 9月23日
2039 3月21日 9月23日
2040 3月20日 9月22日
2041 3月20日 9月23日
2042 3月20日 9月23日
2043 3月21日 9月23日
2044 3月20日 9月22日
2045 3月20日 9月22日
2046 3月20日 9月23日
2047 3月21日 9月23日
2048 3月20日 9月22日
2049 3月20日 9月22日
2050 3月20日 9月23日
2051 3月21日 9月23日
2052 3月20日 9月22日
2053 3月20日 9月22日
2054 3月20日 9月23日
2055 3月21日 9月23日
2056 3月20日 9月22日
2057 3月20日 9月22日
2058 3月20日 9月23日
2059 3月20日 9月23日
春分の日 秋分の日
2060 3月20日 9月22日
2061 3月20日 9月22日
2062 3月20日 9月23日
2063 3月20日 9月23日
2064 3月20日 9月22日
2065 3月20日 9月22日
2066 3月20日 9月23日
2067 3月20日 9月23日
2068 3月20日 9月22日
2069 3月20日 9月22日
2070 3月20日 9月23日
2071 3月20日 9月23日
2072 3月20日 9月22日
2073 3月20日 9月22日
2074 3月20日 9月23日
2075 3月20日 9月23日
2076 3月20日 9月22日
2077 3月20日 9月22日
2078 3月20日 9月22日
2079 3月20日 9月23日
2080 3月20日 9月22日
2081 3月20日 9月22日
2082 3月20日 9月22日
2083 3月20日 9月23日
2084 3月20日 9月22日
2085 3月20日 9月22日
2086 3月20日 9月22日
2087 3月20日 9月23日
2088 3月20日 9月22日
2089 3月20日 9月22日
2090 3月20日 9月22日
2091 3月20日 9月23日
2092 3月19日 9月22日
2093 3月20日 9月22日
2094 3月20日 9月22日
2095 3月20日 9月23日
2096 3月19日 9月22日
2097 3月20日 9月22日
2098 3月20日 9月22日
2099 3月20日 9月23日
2100 3月19日 9月22日
2101 3月20日 9月22日
2102 3月20日 9月22日
2103 3月20日 9月23日
2104 3月19日 9月22日
2105 3月20日 9月22日
2106 3月20日 9月22日
2107 3月20日 9月23日
2108 3月19日 9月22日
2109 3月20日 9月22日

円グラフの色分け

近頃、Fanfiction Update Notifierなるものをやっています。ネット上に存在するファンフィクションの更新をTwitterで通知するものなのですが、その中で、

上のようなグラフを表示するページがあります(各原作別のポスト数です)。それにしてもギャルゲーが多いです(笑)

さて、このグラフ、Google Chart Toolsを使っていまして、色を一色だけ渡すと、上のように自動的に明度を変化させて色分けしてくれます。

ただ、これだとちょっと見にくいかもしれないので、巧い具合に色分けすることを考えました。

上のグラフは項目数が20なので、360 / 20 = 18°ずつ色相をずらしていけばOK、と最初は思ったのですが、これでは大間違いですね。

なぜかと言うと、20項目目の342°で、殆ど最初の0°と同じ色になってしまうからです。つまり、赤橙黄緑青藍紫と色相環を廻って、最初の赤に戻ってしまうわけです。

ですから、最後がちょうど紫になるように、( 360 – 40 ) / 20 = 16°ずつ回転させることにしました。

これでとりあえず完成、なのですが、せっかくなので、WikipediaのHSV色空間を参考に、与えられた数だけ色を返す関数を考えてみました。

function getColors( num, _s, _v ) {
    var deg = Math.round( 320 / num );
    var h = 0;
    var s = _s / 100;
    var v = _v / 100;

    var tmpArray = new Array();
        
    for( i=0; i < num; i++ ){
        var hi = Math.floor( h / 60 ) % 6;
        var f = h / 60 - hi;
        var p = v*( 1 - s );
        var q = v*( 1 - f*s );
        var t = v*( 1 - (1-f)*s );
        h += deg;
                
        var r,g,b;
        
        switch( hi ){
            case 0:
                r = v; g = t; b = p; break;
            case 1:
                r = q; g = v; b = p; break;
            case 2:
                r = p; g = v; b = t; break;
            case 3:
                r = p; g = q; b = v; break;
            case 4:
                r = t; g = p; b = v; break;
            case 5:
                r = v; g = p; b = q; break;
        }
        tmpArray[ i ] = hex( r ) + hex( g ) + hex( b );
    }
    return tmpArray.join(",");
}

function hex( x ){
    var tmp = Math.round( x * 255 ).toString( 16 );
    if( tmp.length < 2 )
        return "0"+tmp;
    else
        return tmp;
}

var hi = Math.floor( h / 60 ) % 6;
となっているのがミソです。Math.round()では上手くいきません。

項目数
彩度(0...100)
明度(0...100)

よろしかったら、お試し下さい。