トップ «前の日記(2006-06-05) 最新 次の日記(2006-06-09)» 編集

Public Diary

For antenna, use RSS(RDF) or LIRS.


2006-06-07

ネット プログラマ的エレガンス

例題1. 時計の長針と短針は、12時にちょうどピッタリと重なります。次にピッタリと重なるのは何時でしょう。

[「Life is beautiful: できるかぎりエレガントな解法を見つけて「うっかりミス」を減らす」よりより引用]

まぁ12時間に11回…というのは発見的手法による部分が大きいような気がするので、ここは順当に。

  • n時m分 (0≦n<12, 0≦m<60) の短針の位置…30n+0.5m (度)
  • n時m分 の長針の位置…6m (度)

というわけで、30n+0.5m = 6m から、m = 6n/1.1 となるような時刻に、長針と短針は重なる、ということになる。たとえば、n=1 として、m=5.4545... なので、12時の次は1時5分27秒2727...に長針と短針が重なるらしい。

算数的には上の式でOKなんでしょうが、実際の時計を考えてみると、27秒2727...という循環小数で表現されるような時刻が表現されることはありません。スムーズ秒針タイプではない従来の時計であれば秒単位でチクタクと動くので、5分27秒から5分28秒に切り替わる瞬間に一瞬で追い越すことになります。従って、この手の問題では秒未満の単位を切り上げて1時5分28秒、数式で表すとガウス記号なんかも使ってn 時[6n/1.1]分[(6n/1.1-[6n/1.1])*60+1-δ]秒と答えるべきかもしれません。もしくは、毎秒に長針と短針が一瞬で位置を変えるとしたら、12時の次に長針と短針が重なるのは24時(≒0時)ということになります。さてはて。

引用元の「エレガントな解法」にはこういう考察までを含めても良いでしょう。単にパズル的に考えれば12時間に11回重なるから…という「一見エレガントっぽい解法」を思いつきますが、「12時間に11回重なる」という仮定がそもそも正しいかどうか検証しないまま計算を始めて、結局解法がキレイすぎるのに満足してしまう人が多いから「実際には重ならないじゃん」というバグにぶつかるかもしれません。あとは、「12時間に12回重なるんじゃないの?」とか疑問を持ち始めると困るねぇ、とか。こういう問題は、愚直な方法かもしれませんが、いったんシンプルな方法で考えたほうが、いろいろ可能性を考えられるような気がします。

余談ですが、書いててアキレスと亀を思い出した。実は長針と短針って永遠に重ならないんじゃ…?

(追記)

要するに、あとから簡単に検証できるような(=説明が容易な)思考のプロセスを日頃から心がけないとデバッグが大変になるし、与えられた条件にぴったりな無駄のない解法を追い求めすぎると「ちょっとした変更」に耐えきれないロジックになりやすいと思います。

ソース中のコメントを読んでも頭をひねらないと理解できないようなパズルのようなプログラムをたまに見かけますが、それはやっぱり自分の趣味で作るものならともかく、仕事やオープンソースなど複数人で仕事をする上では避けるべきでしょう。多少の動作効率の悪さを犠牲にしてでも簡素さによる開発効率の向上をとったほうが良いと思うのです。

(追記2)

調子に乗って問題2も同じアプローチをやってみる。

例題2. サイコロを2個、順番に投げることにします。1つ目のサイコロの目の方が2つ目のサイコロの目より大きい確率を求めてください。

[「Life is beautiful: できるかぎりエレガントな解法を見つけて「うっかりミス」を減らす」よりより引用]

これは言葉で説明するのは面倒なので、実際にCっぽく。

#include <stdio.h>
#include <stdlib.h>

int main(void) {
   int i, j, ans=0, total=0;
   for (i=1; i<=6; i++) {
      for (j=1; j<=6; j++) {
         if (i>j) ans++;
         total++;
      }
   }
   printf("%02d percent.\n", ans * 100 / total);
   return EXIT_SUCCESS;
}

これも基本は愚直に。確かに単純な計算より効率は悪い。その代わり、絶対に漏れがない。確率計算を正しく理解してなくても、これなら間違いようがない。こういうのがプログラマ的エレガンスだと思うんですがね。せめて (1+2+3+4+5)/36 とかかな。

ちなみにプログラマ的テクニックであれば、賽子オブジェクトを作ったりとか、モンテカルロ的手法とか、Mathematicaみたいなのを一から作ってみるとかまぁいろいろ思いつくのですが、今回求められているのはエレガンスですからね。

本日のツッコミ(全4件) [ツッコミを入れる]
やまだ (2006-06-08 00:49)

数学的で、わかりやすい、一番良い回答だと思います。<br>時間と分で2つ変数を定義する発想が目からウロコでした。

wassy (2006-06-08 02:14)

もっとも、<br>* 変数を増やすこと自体に問題がある場合<br>* とにかく効率が良いものが求められる場合<br>などがあるので、一概にこのアプローチが良いとは言えません。そういった場合を除けば、一般的にプログラマにはトリック的な解法は求められてないと思います。

光希 (2006-06-08 04:32)

横から失礼します。<br>このサイトの管理人の高校時代の先輩です。<br><br>ボクもプログラムとNWエンジニアを4年やっていたので、ソースの中で筆者が言いたいことは十分理解できます。<br><br>ITの世界では、プログラムミスをいかに無くし、安定したシステム構築を目指すことが最優先されるため、数学的には考察不可能な場合が出てくるのは当然です。<br><br>ソース中では「中学程度の数学で十分解ける」と書かれていますが、まず中学数学で「無理数」や「循環小数」について深く考察していないのだから、ソース中の「エレガント」の意味がよく伝わってきません。<br><br>ご承知の通り、物理学は数学を道具として使いますが、虚数項の厳密な吟味は必ずしも行いません。<br>「現実に存在しないから」の一言で片付けられます。<br><br>プラグラマから見た「エレガント」と、数学的視点から見た「エレガント」が必ずしも一致しない(しないほうが多い)のは経験者なら誰でも知っているハズです。<br><br>あとで修正する時に、分かりやすいようにコメント文を入れたり、余計な関数を定義したりする必要がしばしばあります。<br><br>ですが、数学的には全く意味がないどころか、テストに書けば減点は避けられません。<br><br>無理数や循環小数は日常生活で体験できるものではないでしょう。<br>「明日は14時(10+ルート2)分に駅前に集合ね。」<br>という会話が成立するのであればまた議論は別になる。<br><br>量子力学で、扱う粒子の大きさを無限大にすればほぼ古典力学と一致すると言います。確かめたことがないので、確証はありませんが、関数の極限などと同じで、実際にその数を体験できないからこそ、無理数や循環小数や極限という概念が数学に導入されてきたのです。<br><br>大工さんが家を建てる時に、微分積分してるでしょうか?<br>設計段階では微積をするかもしれませんが、ルート3の長さの木を用意しろと言われても大工さんは困ります。<br><br>やはりそこは理学部と工学部の違いみたいなもんでしょう。<br>理学部はルート3をそのまま「ルート3」と書きますが、工学部の建築あたりでは「1.73」で計算しているハズです。<br>発見的手法(12時間で11回重なるのは自明だと思う手法)は数学的厳密性に欠けています。<br><br>というわけで、長くなりましたが、(問い1に関してだけですが)数学的視点からみれば、wassyさんの解答は完璧(というか模範的解答)でしょう。東大の後期あたりで出題すれば面白い。<br><br>ただ、wassyさん本人も書かれていますが、プログラムという性格上、複数人で共同作業するのですから、余分な変数が増えたり、手間がかかっても、「障害」を防ぐことが第一だと思います。変数が増えるのは一見、反エレガントのようですが、行列なんかを処理して使えば、逆に簡単になったりしますし。<br><br>とにかくソース中の「エレガント」の定義をハッキリさせてほしいと思う。<br><br>理学部と工学部、事務職とプラグラマでは「エレガント」の定義自体が異なります。<br><br>また、「エレガント」に走りすぎると、足下をすくわれることがあるので、十分にご注意を(笑)。<br><br>光希(理学部物理学科卒・現在京理受験生)より、敬愛する後輩へ。

wassy (2006-06-08 06:48)

コメントありがとうございます。<br>脊髄反射的な感想:<br>(1)おぉ、なげー。すげー。<br>(2)tDiaryってこんな長めのツッコミも投稿できるんだ、すげー。<br><br>エレガントかどうかについてはそもそも捉え方が複数あるように、数学的に厳密であるべきかどうかについても人それぞれの意見はあるでしょう。厳密かどうかという文脈においては、「そもそも時計の長針と短針が重なるとはどういう意味か」まで考えないといけないでしょうし。<br><br>ふと思ったんですが、「長針と短針が n% 以上重なっている時間」を求めようとするとこの解法がきっと一番考えやすいだろうと思います。改造の容易さ+ソースの見やすさの妥協点を探るというのが一番のエレガンスなのかなぁと思います。



1980|03|
1986|04|
1998|04|
2002|01|11|
2003|03|04|05|07|08|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|02|03|04|06|07|08|11|12|
2008|01|02|03|04|06|07|08|09|