IEEE Specturm 09.20を眺めていたら, AxidrawというXYプロッターの記事があった. それに下のような図があったので, その機構を考えてみた.
中央に左右に広がる基盤があり, これは固定されている. その左端と右端にそれぞれモーターと同軸で回転するプーリーがある(L, R). 縦に長い板は, 縦方向を保ったまま, 上下左右に移動し, 下に自由に回転するプーリーMがあって, その辺りに上げ下げできる筆記具がある. それでXYプロッターになるわけだ.
3個のプーリーは, 中央の4個の補助プーリーを介して1本のベルトで結ばれており, ベルトの最後は, 縦の板の上端である.
まずLを固定し, Rの周囲が長さaだけ右回転したとする. その際, 縦の板が上下だけに動いたとすると, 上端に左側のベルトもaだけ弛むが, Lが回転しない約束なので, その弛みは上下の板を右へ移動して吸収することになる.
というわけで, Rがaだけ回転すると, 上下の板は右へa/2, 下へa/2だけ移動する. そうすると, 右上部分のベルトは, x1もy1もa/2だけ短くなり, Rはaだけ回転し, 右下部分ではx1はa/2短く, y0はa/2長くなるので, Rから出たaはMを回って左下へ行く.
左上はy1がa/2短く, x0がa/2長くなるから, 予定通りLは回転しない. 左下はx0もy0も伸びるが, その分だけMから貰うので万事うまくいく.
つまりRの右回転は上下板を右下へ移動させ, 左回転は左上へ移動させる. 同様にLの左回転は上下板を左下へ移動させ, 右回転は右上は移動させる.
左右だけの移動, 上下だけの移動は, LとRの回転を合成すればよい.
なるほどうまい仕掛けであった.
2020年10月1日木曜日
XYプロッター
2020年8月29日土曜日
恒星時計
恒星時計(こうせいじ-けい)を実装した. ここからダウンロードできる.
ダウンロードした時から走行し, 下の上の図のような形である. 右上のディジタル表示は現在の太陽時の年月日と時刻(時, 分, 秒). 左上の表示は現在の(地方)恒星時の時, 分, 秒である.
中央の時計がその恒星時をアナログ的に表示する. 恒星時は0時から24時まであるが, 時計の文字盤には時の表示が12個しかなく, この時計の時針は恒星時の12時間で1回転する. その代わり, 恒星時の正時に文字盤の時針と反対の位置の文字が12増えたり12減ったりする.
この図では, 時針は9時と10時の間にあり, そこから左周りに6時間は過去の時刻を, 右周りに6時間は未来の6時間を示す.
時計の周囲の四角形の内部をクリックすると, 時計は太陽時の秒が繰り上がった時点で停止し, その時刻での恒星時がミリ秒まで表示される. 下の時計の図は停止中の様子を示す. もう一度クリックすると走行を再開する.
時計の盤面に, 北極星を中心とする北天の星図が表示してある. この星図は星座早見盤の自作キットから使わせて頂いた. この星図は恒星時の24時間で左周りに1回転する. 通常の時計の6時相当する真下を時角0h, 3時に相当する右を時角6h, 真上を12h, 左を18hとして北天が日周運動する.
星図に薄く天の川が表示されている. 天の川が天の北極に近いのが赤経0hの方向で, 中央からその方向に辿ると天の川の中にカシオペアのWがあり, さらに外周へ探すとペガススの四角がある. その少し外の緑の点が春分点(Υ)で, 春分点の時角と恒星時が一致しているのが分る.
この時計の星図の下2/3くらいの横長の楕円が, その時刻に見える星空になる.
次の大きな円は星図の元の図である. 右の方の3本の左向き矢印の先の星が, 上からうお(Pisces)のλとω, くじら座(Cetus)のιである. その下の図がうお座とくじら座で, これから見ると春分点はこれら3つの星の中央にあり, 恒星時計の文字盤の春分点をその位置にマークした.
この時計では, ある太陽時の日時, 時刻の恒星時を知ることが出来る. 時計の枠内でキーボードから数字を入力すると時計が停止し, その数字を空白で区切りながら, 年, 月, 日, 時, 分, 秒と入力すると, 対応する恒星時が示される.
最初の時計の図の時刻では, 2020⨆8⨆8⨆11⨆53⨆9と入力すると, 左上に恒星時が現れ, 文字盤にはその時刻に現れる星空が表示される. ミリ秒の値は少し違うが.
恒星時の計算は暗算としてはこのようにする. 3月21日頃の春分には太陽が春分点にいるから, 昼頃, 太陽の南中時が恒星時の0h. それに太陽時の正午からの差を加える. 太陽の赤経は, 1ヶ月に2h増えるから, 例えば8月8日, 立秋の頃なら, 正午に太陽が南中するとき, 春分点は4ヶ月半近く, 9hだけ時角が離れる. つまり正午が9hである. 従って8月8日, 21時だと9h+9h=18hとおよその見当がつく.
しかしこの方法は時計には使えない. 理科年表や天文年鑑には, その年の毎日の世界時0時のグリニジ視恒星時の表があり, インターネットで探すと多くの関連ページが見付かる.
国立天文台の グリニジ恒星時計算HPで2020年1月1日から世界時0時の恒星時を1ヶ月表示したのの一部が次の図である. (出力時刻もGMT)
理科年表では0.1秒, 天文年鑑では0.1分まで表示されているのが, このHPでは0.001秒の精度で表示される.
インターネットにあるいくつかの恒星時計算アルゴリズムで, 2020年1月1日世界時0時の恒星時を計算して見ると, どういうわけかこの表の値にならないので, この値が得られるような数値を自分で計算した.
あるユリウス日がjd 0の日時での恒星時st 0が既知の時, ユリウス日がjdでの日時での恒星時stは
st=st 0+1.0027378×(jd-jd 0)
だから, jdとstのペアがあれば, あるjd 0でのst 0が計算できる.
jd 0としては, 何かの恒星時計算アルゴリズムにあった切断(?truncated)ユリウス日の起算時点(jd=2440000.5)を使うことにした. つまり1月中の0時のjdと対応するstから31個の
st 0=st-1.0027378×(jd-2440000.5)
を求めて平均し, st 0を作る. 恒星時の表はミリ秒のリスト
(226 778 330 882 435 990 548 109 672 237 802 365 925 481 34
585 136 688 243 801 362 925 487 49 609 167 721 274 824 374 924))
を用意し, ミリ秒が減った時は前日の秒に237秒を, 増えた時は236秒を足し, ミリ秒その日の値としながら, 1月の恒星時を入力した. そして定数st0を得たが, それで12月の恒星時を計算すると, 天文台の表と多少ずれるので, 12月についても同様に計算し, 両者の定数の平均を使うことにした. つまり
st=0.6712386796084648+(1.00273791×(jd-2440000.5))
を使う.
この値を使う, Pythonで書いた恒星時計算プログラムは次の通り.
def jd(y,m,d):
def leap(y):
return y%400==0 if y%100==0 else y%4==0
return y*365+y//4-y//100+y//400-(489*(13-m)+26)//16\
+((1 if leap(y)else 2)if m<3 else 0)+d+1721426
def sidereal(y,mo,d,h,mi,s):
def frac(a):
return a-int(a)
ut=jd(y,mo,d)+(h-9+(mi+s/60)/60)/24-0.5 #-9: to GMT
st=0.6712386796084648+1.00273791*(ut-2440000.5)+9/24
#+9/24: to JST
h=frac(st)*24
m=frac(h)*60
s=frac(m)*60
ms=frac(s)
return [int(h),int(m),int(s),ms]
print(sidereal(2020,1,1,9,0,0))
出力は[15, 40, 28, 0.20889443391934037]で, 前掲の1月1日の値 6h40m28.226sに9時間足したのにかなり合っている.
このような恒星時計が, 宮沢賢治の「銀河鉄道の夜」に出て来る 白鳥, わし, さそり, ケンタウルスなどの駅のホームや操車場やアルビレオ観測所で動いていると想像すると楽しい.
「ケンタウルス, 露をふらせ.」
2020年8月26日水曜日
老子
夏目漱石が護国寺で運慶を見たとか(「夢十夜」の「第六夜」), 芥川龍之介が市電から寒山, 拾得を見とか(「寒山拾得」)いう話があるが, 寺田寅彦が「電車で老子に会った話」は趣きが異なる.
つまり寺田先生は「インゼル・ビュフェライ叢書」にアレクサンダー・ウラールが訳した『老子』を見付けて購入し, 電車の中で読み始めたら面白くなり, 数回の車中で読了したという. 日本語の解説より分りやすかったらしい.
私は老子は近寄り難く避けていたが, 昨年末, 中国深圳で少し話をしたら, 謝礼として為書きのある書を貰った. それに「大道至簡」と書いてあり, 調べたところ老子の言葉らしいことが判明. それではというので老子を読むことにした.
早速読んだのは
池田知久 「老子 全訳注」 講談社学術文庫
だが,
保立道久 「現代語訳」 老子 ちくま新書
もインターネットに
保立道久の研究雑記
『老子』、現代語訳・原文・読み下し
があったので, それも参考にする.
老子は第1章から第37章までの道経と, 第38章から第81章までの徳経の, 計81章で構成される. 今回は夫々の最初の章の最初の文を例にする.
池田本では, 第1章の最初は
道可道也、非恆道也。名可名也、非恆名也。
道の道とす可きは、恒の道に非ざるなり。名の名とす可きは、恒の名に非ざるなり。
道というものがあれこれ唱えられている中で、道として定立することのできるわたしの道も、恒常不変の真の道であるとは言えない。道についての名(理論)があれこれ説かれている中で、道の名(理論)として表現することのできるわたしの名(理論)も、恒常不変の真の名(理論)であるとは言えない。
一方, 保立本は
道可道也、非恒道也。名可名也、非恒名也。
道の道(ゆ)くべきは、恒なる道に非(あら)ざるなり。名の名づくべきは、恒なる名に非ざるなり。
普通に行く道と、ここでいう「恒なる道」はまったく違うものだ。普通に名づけることができる名と、ここでいう「恒なる名」もまったく違う。
どちらも, 特に後者は短いだけあって, 禅問答のようだ.
次に第38章は, 池田本で
上徳不徳、是以有徳。下徳不失徳、是以徳无。
上徳は徳ならず、是を以て徳有り。下徳は徳を失わず、是を以て徳无し。
そもそも最上の徳は、世間的な徳とは正反対である。だからこそ、真の徳がある。下等な徳は、世間的な徳を捨てることが出来ない。だからこそ、真の徳がないのだ。
保立本は
上徳、不徳是以有徳。下徳、不失徳是以無徳。
上徳は、徳ならずして是(ここ)を以て徳あり。下徳は、徳を失わずして是を以て徳なし。
「道」から発した最上の「徳(いきおい)」(上徳)は徳(はたらき)がないようにみえて大きな徳(いきおい)があり、そうでない「下徳」は徳(はたらき)を失っていないようだが実は徳(いきおい)がなくなっている。
同じような感じだ. もともと道とか徳とかが抽象的だから, 説明が難しくて当然であろう.
寺田先生がドイツ語訳で分ったような気分になったというので, 英訳を探すと
The Tao Te Ching by Lao Tzu
馮家福 Jane English
があった. 表題のTao Te ChingはTao=道, Te=徳, Ching=経のことだ.
道の説明は次のようだ.
One
The Tao that can be told is not the eternal Tao.
The name that can be named is not the eternal name.
また徳の方は
Thirty-eight
A truly good man is not aware of his goodness,
And is therefore good.
A foolish man tries to be good,
And is therefore not good.
上徳と下徳がgood manとfoolish manになっているのが凄い. なるほど日本語の説明より具体的である.
インターネットでさらに探すと, 寺田先生が読んだAlexander Ularの訳の一部があった.
Die Bahn und der rechte Weg des Lao-Tse
Alexander Ular
DER ERSTE SPRUCH
Die Bahn der Bahnen ist nicht die Alltagsbahn;
Der Name der Namen ist nicht der Alltagsname.
38章は掲載されていなかった.
ドイツ語を和訳するのは簡単だ. 複数をたちと訳せば
道たちの道は毎日の道ではない
名たちの名は毎日の名ではない
でもこれが何を言おうとしているかは, やはり分らない.
結果的には池田本の訳が一番しっくりする. つまりドイツ語が分り易いというのも章によるのではないかということである. 沢山の訳を比較すれば, 段々と理解しやすくなるという当然の結論に達する.
ところで論語だが, 大学の先生方がよくいうのが
学而不思則罔 思而不学則殆
学びて思わざれば則ち罔(くら)し、思いて学ばざれば則ち殆(あやう)し。
これは罔や殆が理解を妨げている. 私の持っている論語の英訳を見ると
The Analects of Confucius
The Master said: `Learing without thinking is useless. Thinking without learning is dangerous.'
のように, uselessとdangerousになっている.
つまり, 数学の公式を学んでも, それを十分に理解しなければ, 使えない. また思い付いた名案も, その基礎が分っていないと, 迷案になるというようなことだ.
この場合は英訳が分り易い.
2019年6月17日月曜日
SDGsロゴの錯視

カラフルな17個の扇形で囲まれていて, その隣り同志の扇形の間には白い間隔がある. この間隔は等幅のようでもあるが, なんとなく内側の方が広く見えるから不思議だ. 扇形が内側で狭くなるのにつられて, 間隔は逆に内側が広く感じるのであろうか. これを取り敢えずSDGsロゴの錯視といおう.
そこだけ取り出して描いたのがこの図だ.

これだけ眺めると, 外縁での間隔goと, 内縁での間隔giは別に違っては見えないが, 上の図ではたしかに錯視が起きる.
北岡さんの錯視のカタログ
間隔の幅Gを変えたり, 内縁半径Rを変えたりして, 錯視の起きる範囲をしらべたいと思い作ったウェブアプリが下だ. 左下は+, -でRを加減する. Rをクリックすると標準に戻る. 右下はGについての同様なメニューである.

錯視の起きる起きないの境界は微妙なので, はっきりしたことはいえないが, 錯視はほどほどの間隔の時に起き易いらしい. このウェブアプリは出来たばかりで枯れていないので, うまく動作しない時はご容赦を乞う.
2019年6月15日土曜日
Tパズル

Tパズルは上の図の左の家型の五角形を4分割したなんの変哲もないピースを組み合わせ, 指示されたいろいろな形を構成するパズルである. 最初の指示が図の右のT型を作るので, Tパズルというらしい. 元の家型の時のピースの縦横の辺が, T型では斜めになっているので, この問題は最初からいささか意地が悪い.
インターネットで探すと, 下の図のように様々な問題があるので, ちょっとやってみたくなり, 百円ショップで小学生の使う工作用紙を購入し, 自作した.

自作するには下のような図を描き, 同じ形の表と裏を貼り合せた. この升目が工作用紙の1cmである. 各辺の内側の数値は, T型の足の幅を基本単位とするその辺の長さで, 出来上がったピースにも同様に記入してある. この図では各ピースが繋って描いてあるが, 作業に便利なように, 本当はピースの間に隙間があった.

実際に出来たものは下の写真の通りで, 旅館で販売しているものより遥かに大きい. 市販のものは辺に寸法が書いてなく, いわばアナログ仕様だが, それでは√2と1.5の区別もつき難そうだ. 私のは寸法が記入してあるので間違うことはない.

さて, 私のやり方をちょっと種明かししよう. 上にあった問題の1番のT型と12番の家型を例にする.
型の図をPCに読み込み, 適当に拡大する. 角に順に番号をつける. マウスを動かし, それぞれの角の座標を読み取る. 座標はそれほどの精度を要求しない. それが下の2枚の図だ.


次にこの形を角を頂点とする三角形に分割する. T型では((0 6 7) (0 1 6) (4 5 6) (4 6 1) (4 1 2) (4 2 3)), 家型では((1 2 3) (1 3 4) (1 4 0))とした. そして夫々の型で, 座標と三角形の頂点の組をSchemeのプログラムで読み込む. その値を確認するため, プログラムはSchemeのgraphics機能を利用して出力する. それが次の図である. T型の足など多少怪しいがまぁよしとしよう.


続いてこの座標系において相隣る2点間の距離を計算する. それぞれの点のx, y座標が既知だから, Pythagorasの定理で距離は分る. 更にこの座標系での各々の三角形の面積を求め, 形全体の面積を算出する.
x0,y0; x1,y1; x2,y2を頂点とする三角形の面積は, 行列式
|x0 y0 1|
|x1 y1 1|
|x2 y2 1|
の半分であることは高校生のころ, 解析幾何の授業で学んだのでそれを使う. ただ三角形の頂点を時計廻りか反時計廻りかで辿ると面積が正負になり, いつも怪しくなる. そういう時は(0,0) (1,0) (0,1)の三角形で試みると反時計廻りで正になると分るが, プログラムでは絶対値をとってから合計している.
一方, 4個のピースの面積の和は6平方基本単位であるから, 今得られた面積を6で除して平方根をとると, それが座標系の長さと基本単位の比で, それぞれの辺が基本単位に変換できる.
これでT型を計算すると
(2.8999986101772106 .8652158634699881 .9960165420683874 2.935646424994157 1.0222409451321806 .9698622511287074 2.8779826845214163 1.0169988220903106)
が出てきて, 番号0の角からの順の辺の長さが, 3,1,1,3,1,1,3,1であることが分る. (0.865を1にするには勇気がいるが.)
また家型の方は
(1.418992855950106 2.022013340831029 1.993599932770006 1.4095980475184093 2.7873862791620128)
だから, √2, 2,2,√2, 2√2らしいとなる.
ここまで来たら大体の問題はわけもなく解けるが, 実際にやってみると案外微妙な値になる型もあり(1.5か√2かなど), ここに述べた方法が特効薬であるわけでもない. 元々の問題の図もさほど精密ではなく, また中にはこれはどうかと思う問題もあったが, 目くじらを立てることもあるまい.
2019年6月13日木曜日
連分数と近似分数
Martin GardnerがそのThe Unexpected Hanging(ネットで見つかる)の40ページで, 自然対数の底eを近似する, 分母分子とも3桁以内の分数は何かと問うている. その答は覚えやすい878/323 (=2.718266253869969...)だが, その計算法が今回の話題である.
Gardnerは自分では説明せず,
George Chrystal, Algebra An Elementary Text-Book for the Higher Classes of Secondary Schools and for Colleges, Part II. 1906
を見よとしか書いていない. この古典は有名らしく, ネットで探すと読むことができる. (648ページもある!)
しかしこれは, 高木貞治先生の「初等整数論講義」(手元のは第2版だが)の「中間近似分数」の章にそのものずばりの解説がある.
円周率πの同様な近似値355/113は遥かに有名であった. この分数を得るには連分数を使う.
普通の連分数は

の左上のような形である. この元のTexプログラムは
\[a_0+{b_1\over\displaystyle a_1+
{\strut b_2\over\displaystyle a_2+
{\strut b_3\over\displaystyle\ddots+
{\strut b_n\over a_n+\ddots
}}}}\]
\[a_0+\frac{b_1}{a_1}\:\raisebox{-1.3ex}{+}\:\frac{b_2}{a_2}
\:\raisebox{-1.3ex}{+}\:\raisebox{-1.ex}{\ldots}\,
\raisebox{-1.3ex}{+}\:\frac{b_n}{a_n}
\raisebox{-1.3ex}{+}\:\raisebox{-1.ex}{\ldots}\]
この式にあるbiを1, aiを正の整数kiにしたものを単純(simple)連分数とか正則(regular)連分数といい, 右上のように書き, 物の本にはこれだけを扱ううものが少くない.
こういう表示法は空間をとるので, 通常は下の左や右のように書く. kiを部分商という.
こういう連分数から, 878/323や355/113のような近似分数が得られる.
円周率πの連分数展開で得られる部分商は7, 15, 1, 292, 1,...
で, この値を順に求める方法が「初等整数論講義」に載っている. これを見ると連分数は, Euclidの互除法に似ていることが分る.

部分商kの値から,

の関係でp, qが計算できる. πの場合のk, p, qは下の表の通り. p/qは連分数の展開を途中で止めたときの分数の値であり, 下の計算のようになる. 各行の右は, 左の分数の値とπとの差で, 見て分るように, 正負が交互に現れる.
この表の下の方に, 355/113がある. この近似分数はこうして発見された.

eについては, 今回はPythonで書いて
import math x=math.e ai=[] while len(ai)<12: a=math.floor(x) ai.append(a) x=1/(x-a) print(ai)12:>
すると得られた結果は
[2, 1, 2, 1, 1, 4, 1, 1, 6, 1, 1, 8]
πと同じような表を作ると

しかしこの計算には, 問題の878/323がない. そこで「中間近似分数」が登場する.
p5=106, p6=193,p7=1264
q5=39, q6=71,q7=465
の間には
p7-p5=p6×k6
q7-q5=q6×k6
の関係があるから, pの方は106に193を6回足すと1264になり, qは39に71を6回足すと465になる. この足し算の途中の値rλ, sλを分子, 分母にして計算すると, p5/q5からp7/q7の間の近似値が次々と得られる. これを中間近似分数(intermediate convergents)という.
この計算が次の表である.

そしてこの中に, 問題の878/323があったということだ.
2019年4月22日月曜日
15パズル
ところで, 最近知った15パズルの面白い解き方は, 福岡教育大学の藤本さんの提案する「回転型操作」によるものだ.
2件の論文がある.
情報処理学会研究報告にある「スライドパズルにおける回転型操作とアクセスビリティ」と, 近着の数式処理にある「Loop generatorによる15パズルの最適アルゴリズムとGod's numberについて」.
私は明解な手順には関心があるが, 最短手順には興味がないので, 後の論文は眺めた程度であった. 前の論文も解法は数式処理しシステムGAPを使って記述してあるので, GAPに馴染まぬ凡人にはとんと理解し得ぬ. 以下では私がSchemeで書いたプログラムの考え方を述べる.
この回転操作(英語ではloop generatorというらしい)は, 前回の15パズルのブログと似たやり方だが, 経路の単純なのが取り柄だ. 回転操作の前と後では, 空白は右下隅(15)に置くことにする. (イタリックの数字は場所の番地である.) 回転操作にはa, b, cがあり, それぞれ下の図の赤, 緑, 青の線のようにこまを回す. もちろん反対方向にも回す(回転数にマイナスを付けて示す). 例えばaを1回というと, 0にあったこまは4へ移動, 2回というと8へ移動する. bを1回では, 5のこまは9へ, 2回では13へ進む. 逆回転はは回数を負にする. aを-2回というと, 0が2へ行く.
回転経路が重なるのは, 図の7, 11, 13, 14の場所で, ここを経由して回転だけを使い, こまを任意の場所から他の任意の場所へ移すのである.
例えば5にあるこまを0へ移すには, aを4回して移動先0を13へ移動し, bを2回で移動元を13へ移し, aを-4回して最終目的地0へ動かす.
移動元と移動先が同じループに属しているといささか面倒だ. 例えば4から1へ移動するには,どちらもaのループにあるから, あらかじめ4を内側のループ, bかcのaと重ならない場所へ移動しておく. つまりaを3回で, 4のものを13へ; 次にbを-1回で9へ; 移動先の1は, a3回で8へ来ているから, 後2回で13へ進める. そこでbを1回行い, 4にあったものを13へ移す. それからaを-5回行うと, I1の位置へ行くわけである.
こういう手順が判明したから, aのループにある, 上端と左端にあるべき7個(4,3,2,1,,5,9,13)のこまを目的位置へ移す. この時はb, cのループの場所は自由に使ってよい. その後, bのループの5個(8,7,6,10,14)を揃える. この作業場所はcの10である. bが揃うと残りはcのループの10, 11, 14の番地に11, 12, 15のこまが残るだけになり, もとの配置のパリティが合っていれば, c1回かc-1回で完成する.
プログラムは後で示すことにし, 実際に走ったところを見よう. 下の図の左上(A)が最初の状態で, 空白はすでに右下15にある.
こま4はbのループにあるから, (a 7)(b 2)(a -7)を行うと, (a 7)で4の目的地3が13へ行き, (b 2)で4が13へ行き, (a -7)で4が3へ移る. (B)になる.
こま3は元々は目的地2にあったのだが, 4の移動のとばっちりで0にいる. そこで(a 4)(b -1)(a -4)(a 6)(b 1)(a -6)を実行する. (a 4)で3を13へ, (b -1)で9へ, (a -4)でaループを一旦元へ戻す. 改めて2の位置を(a 6)で13へ運び, (b 1)で3をそこに置き, (a -6)で(C)のように3が収まる. (a -4) (a 6)と続けるのは無駄だが, プログラムが明瞭になるからこのままにしてある.
こま2は3に連られて目的地に来ていたからなにもしない. (D).
次は1の番だ. またaのループにあるから, (a 3)(b -1)(a -3)(a 4)(b 1)(a -4)の前半で9の位置に置き, 後半で0へ移す. (E).
5はaループだが同時にbループでもある. この場合は, bの中へ入れればよいが, 私のプログラムでは(b 2)で5へ入れることにしている. それから(a 3)(b 2)(a -3)で(F).
9は(a 1)(b -1)(a -1)(a 2)(b 1)(a -2)とする. (G).
次の13もa, cループだから, 一旦10へ移す. (c 1). その後(a 2)(c 1)(a -2) で(H). ここまででaループの7個が終わる. 以後はaループの7個には影響しないように注意.
(H)を見ると8はb, cループにあるから, (c -1)で10へ避難. それから(b 5)(c 1)(b -5). (I)になる.
7はcループにあるから簡単だ. 6の場所を(b 4)で14へ. それから(c 1)(b -4)で(J). 幸運にもついでに6, 10も揃うから(K),(L)も同じ図だ.
14はb, cループ上にいるから(c -1)で10へ引上げ, (b 1)(c 1)(b -1).
最後は11, 12, 15のcループ. 11が10にあれば何もしない. 11にあれば(c 1), 14にあれば(c -1)だ. (N).
全体の手は
(a 7)(b 2)(a -7);4 (a 4)(b -1)(a -4)(a 6)(b 1)(a -6);3 2 (a 3)(b -1)(a -3)(a 4)(b 1)(a -4);1 (b 2)(a 3)(b 2)(a -3);5 (a 1)(b -1)(a -1)(a 2)(b 1)(a -2);9 (c 1)(a 2)(c 1)(a -2);13 (c -1)(b 5)(c 1)(b -5);8 (b 4)(c 1)(b -4);7 6 10 (c -1)(b 1)(c 1)(b -1);14 (c -1);11 12 15
であった.
一方, Schemeによるプログラムはこんな具合いだ.
(define bs (range 0 16))
は盤面のこまのリスト.
(define atab '( (0 1 2 3 4 5 6 7 8 9 10 11 12 13 14) (4 0 1 2 8 5 6 3 12 9 10 7 13 14 11) (8 4 0 1 12 5 6 2 13 9 10 3 14 11 7) (12 8 4 0 13 5 6 1 14 9 10 2 11 7 3) (13 12 8 4 14 5 6 0 11 9 10 1 7 3 2) (14 13 12 8 11 5 6 4 7 9 10 0 3 2 1) (11 14 13 12 7 5 6 8 3 9 10 4 2 1 0) (7 11 14 13 3 5 6 12 2 9 10 8 1 0 4) (3 7 11 14 2 5 6 13 1 9 10 12 0 4 8) (2 3 7 11 1 5 6 14 0 9 10 13 4 8 12) (1 2 3 7 0 5 6 11 4 9 10 14 8 12 13))) (define btab '( (0 1 2 3 4 5 6 7 8 9 10 11 12 13 14) (0 1 2 3 4 9 5 6 8 13 10 7 12 14 11) (0 1 2 3 4 13 9 5 8 14 10 6 12 11 7) (0 1 2 3 4 14 13 9 8 11 10 5 12 7 6) (0 1 2 3 4 11 14 13 8 7 10 9 12 6 5) (0 1 2 3 4 7 11 14 8 6 10 13 12 5 9) (0 1 2 3 4 6 7 11 8 5 10 14 12 9 13))) (define ctab'( (0 1 2 3 4 5 6 7 8 9 10 11 12 13 14) (0 1 2 3 4 5 6 7 8 9 14 10 12 13 11) (0 1 2 3 4 5 6 7 8 9 11 14 12 13 10)))
はa, b, cそれぞれの回転で, 0,...のこまがどこへ行くかを示す. atab, つまりaの表は, 最初の行が(a 0)に対応. 同じ場所に留まる. 次の行(4 0 1...)はaの1回転で, 0は4へ, 1は0へ移ることを示す. 最後の行(atab[10])は-1回のものだ. b, cについても同様.
(define (rotate tab) (let ((b (make-list 16 0))) (do ((i 0 (+ i 1))) ((= i 15)) (list-set! b (list-ref tab i) (list-ref bs i))) (set! bs (list-copy b)) 'ok)) (define (a n) (rotate (list-ref atab (modulo n 11)))) (define (b n) (rotate (list-ref btab (modulo n 7)))) (define (c n) (rotate (list-ref ctab (modulo n 3))))
(a n)がaのループをn回実行する. aの表から使うべき行をとりだし, rotateへ. rotateは16個のリストbを用意し, bs内のこまの番号をbへ移して最後にbをbsへ戻す.
(define ps0 '(4 3 2 1 5 9 13)) (define qs0 '(3 2 1 0 4 8 12)) (define ps1 '(8 7 6 10 14)) (define qs1 '(7 6 5 9 13))
ps0はaループのこまの番号, qs0はその目的地. ps1とqs1はbループのものである. 次がいよいよ解くプログラムである.
(define (solve2) (do ((i 0 (+ i 1))) ((= i 7)) (let* ((p (list-ref ps0 i)) (q (list-ref qs0 i)) (r (elemindex p bs)) ) (if (not (= r q)) (begin (set! r (case r ((7) (b 2) 5) ((11) (c 1) 10) ((14) (c -1) 10) ((13) (b -2) 5) ((0 1 2 3 4 8 12) (let ((l (length (member r qs0)))) (a l) (b -1) (a (- l)) 9)) (else r))) (if (= r 10) (let ((l (- 8 i))) (a l) (c 1) (a (- l))) (let ((l (- 7 i))) (a l) (b (length (member r '(6 5 9)))) (a (- l))))))))
ここまでがaループのプログラムで, 7個についてdoループを回す. letへ来て, pはこまの番号, qは目的地, rは現在地である. r=qなら何もしない. そうでないならcase式へ来て, r=7なら(b 2)を実行, こまを5へ, という風に読む. rが0,1,2,3,4,8,12なら, rがqs0にある位置から後方の長さlを知り, (a l) (b -1) (a (- l))を行ない, こまを9へ移す. 上のいずれでもなければelseへ来て, 回転はせず, rはそのまま. 上のbeginの次の(set! r ...)でrを更新する.
結局移動するこまはcの10かbの5,6,9のどこかにいるから, それらの情報を使い, 目的地へ移動する.
bループのプログラムが以下だが, 説明は同様だ.
(do ((i 0 (+ i 1))) ((= i 5)) (let* ((p (list-ref ps1 i)) (q (list-ref qs1 i)) (r (elemindex p bs))) (if (not (= r q)) (begin (case r ((11) (c 1)) ((14) (c -1)) ((7 6 5 9 13) (let ((l (length (member r qs1)))) (b l) (c -1) (b (- l))))) (let ((l (- 5 i))) (b l) (c 1) (b (- l))))))) (case (elemindex 11 bs) ((11) (c 1)) ((14) (c -1))) (drawbs))
という次第で無事に並べ替られる. なかなか楽しい.






