終点枕崎

プログラミングとvimと音楽と地理

急性腸炎にかかった

急に寒くなったと思えば真夏の天気になったりと、体に優しくないこの頃。風邪が流行っていますね。僕の周りでも数人がダウンしている模様です。

そんな中、空気を読まずに腸炎にかかりました。俗に胃腸風邪とか、ノロウイルスとか言われるやつです。胃腸炎という病気は胃腸に炎症が起こる病気全般を指すっていう中々にガバガバ定義で、原因は大きく分けて2つ、細菌が原因のものとウイルスが原因のものがあるそうです。細菌が引き起こす胃腸炎で、食事が原因になるものは食中毒って言われます。O-157とか有名。原因となるウイルスもいくつか種類があって、ノロウイルスアデノウイルス、他にもたくさんあります。

ウイルスごとの特効薬とかないんで、どのウイルスであっても治療法は変わらないらしいです。なのでどのウイルスが原因か特定する必要はないと。詳しい検査もされませんでした。整腸剤を飲みながら自然治癒を待つしかないらしいです。

同じ病気にかかった人の参考になればということで、病状の経過を記したいと思います。

10/6(金)

サークルに行きました。いつもよりも疲労感が溜まって「ちょっといつもと違うな」と感じました。

10/7(土)

朝起きたら鈍い腹痛が。眠気と倦怠感のせいで、なかなかベッドから起きられませんでした。この時点ではただの腹痛だろうと思っていたのでバイトに行きましたが、いざ働き出すと腹痛と倦怠感で死にそうになりました。4時間働いたのち、上司に言って早めに上がらせてもらいましたが、家に帰るのも精一杯な状態。

ここでカレーを食べるという悪手を打ちました。あとで知りましたが、カレーは胃腸に負担がかかるので胃腸炎の時は食べてはいけないらしいです。

家について秒で眠りに落ちて、3時間後に起床。熱を測ると39.7度。これはまずいことになったと思い、この時間でもやってる病院を探して診察を受けました。
しかし症状を話しても「よくわかんない」と言われる始末。解熱剤と整腸剤を処方されました。

10/8(日)

解熱剤が効いたのか38.5度まで熱が下がりました。何食べても下痢で出て辛いので、食事の量を絞り始めました。夜中に腹痛で叩き起こされた上に吐き気まで出てきました。

10/9(月)

日中は平熱まで下がりましたが下痢が続きます。もう出すものもないはずなのに腹痛と水っぽい下痢だけは続くんですよね。夕方から微熱。

10/10(火)

症状変わらず。夜に吐き気が出ました。絶食を始めました。

10/11(水)

近くの総合病院の外来に行って、血液検査とエコー検査、尿検査を受けました。血液検査の結果は、CRP定量が3.5(基準0.3)と高値。これは炎症反応の度合いを示す度数で、この数値が高いと体のどこかで炎症が起きていることがわかります。「小腸のリンパ節が腫れています。ウイルス性の腸炎でしょう。他の臓器に異常はありません。」との診断でした。土曜日に行った病院でもらった整腸剤が効いてる気がしないので、違う種類の整腸剤を処方してもらいました。

夜、とうとう絶食に耐えられなくなり天一のチャーハンを食べたら案の定死にました。

10/12(木)

ようやく熱が下がりました。下痢も一番ひどかった時期に比べれば少なくなりましたね。

10/13(金)

やっと下痢が止まりました。大学にも少し行きました。全快です。

本来2、3日で治る病気らしいんですけど、多分バイトとカレーのせいで引きずった気がします。バイトやめたいです。

std::arrayの初期化

次のコードをclangで-Wallでコンパイルすると警告が出ます。gccでは出ないです。

clangのバージョン : Apple LLVM version 9.0.0 (clang-900.0.37)

[test1.cpp]

#include <array>
#include <iostream>

int main(int argc, char* argv[])
{
	std::array<int, 4> ar = { 0, 1, 2, 3 };

	for(int i : ar)
	{
		std::cout << i << std::endl;
	}

	return 0;
}

[コンパイル]


clang++ test1.cpp -std=c++11 -Wall -o test1.out

test1.cpp:6:28: warning: suggest braces around initialization of subobject [-Wmissing-braces]
std::array ar = { 0, 1, 2, 3 };
^~~~~~~~~~
{ }
1 warning generated.

中括弧の数が足りないと言っています。次のコードでは警告は出ません。

#include <array>
#include <iostream>

int main(int argc, char* argv[])
{
	std::array<int, 4> ar = { { 0, 1, 2, 3 } };

	for(int i : ar)
	{
		std::cout << i << std::endl;
	}

	return 0;
}

std::arrayの実装を読むと、かなり大雑把に言えば次のようになっています。

template<typename _Tp, std::size_t _Nm>
struct array
{
    _Tp _M_elems[Nm];
    //以下メンバ関数
    //......
};

std::arrayには、publicなメンバ変数である_Tp型配列以外のメンバ変数がありません。さらにaggregateの条件を満たしているため、通常の配列と同様の初期化の仕方が可能になります。ただし、std::arrayは配列そのものではなくて配列を唯一メンバ変数に持つ構造体なので、括弧が二重に必要です。

つまり、一重括弧の方が文法的にグレーなのであって、二重括弧の方が本来正しい記述の仕方というわけです。しかしstd::arrayは_M_elemsよりも前にメンバ変数を持たないため、一重括弧でも意図した通りに動くというわけです。

結論としては、二重括弧で書いた方がいいと思います。

余談ですが、std::arrayでも生配列と同じように0初期化が可能です。

std::array<int, 4> ar = {};
std::array<int, 4> ar = { 0 };

clangでは、前者だとワーニングは出ませんが後者だと出ます。

二次元配列でも同じように初期化が可能です。{}内の要素が足りない場合、
・数値なら0で初期化
・ポインタならnullptrで初期化
・aggregateならこのルールを再帰的に適用
というルールで初期化を行うからです。次のコードも全て0で初期化できますね。

#include <array>
#include <iostream>

struct vec
{
	int x;
	int y;
};

int main(int argc, char* argv[])
{
	std::array<std::array<vec, 4>, 4> ar = {};

	for(const auto& line : ar)
	{
		for(const auto& cell : line)
		{
			std::cout << "(" << cell.x << ", " << cell.y << "), ";
		}
		std::cout << std::endl;
	}

	return 0;
}

[実行結果]
(0, 0), (0, 0), (0, 0), (0, 0),
(0, 0), (0, 0), (0, 0), (0, 0),
(0, 0), (0, 0), (0, 0), (0, 0),
(0, 0), (0, 0), (0, 0), (0, 0),

モンテカルロ木探索でBlokusのAIを作った

タイトルの通りです。モンテカルロ木探索と呼ばれるアルゴリズムの習作として、blokusというボードゲームの対戦AIを作りました。

Blokus

まずはBlokusについてご説明。4人のプレイヤーはそれぞれ自分の色(赤・黄・青・緑)のピースを持ち駒として持ち、4人で順番に持ち駒を20x20のマス目状のボードの上に配置していく。ピースは正方形が1〜5個繋がったもので、全部で21個ある。(余談だが、このように正方形を辺でつなげた図形をポリオミノといい、特にn個の正方形をつなげた図形をn-オミノと呼ぶ。blokusのピースはモノミオ、ドミノ、トリオミノ、テトロミノ、ペントミノからなる。)

ピースを置く際には、以下の制約に従う。

  • 初めの順番が来たら、ボードの角のマスを埋めるようにピースを置く。
  • それ以降は、自分のすでに置いたピースと角で接するように、そして辺で接しないようにピースを置いていく。色の違うピースは辺で接していても構わない。

f:id:uchifuji:20170914235542j:plainf:id:uchifuji:20170914235546j:plain

全員がピースを置けなくなったらゲーム終了。

f:id:uchifuji:20170914235548j:plain

勝ち負けの計算方法は2通りあって、単純に残りの手持ちピースのマス目の総数が少ない人が勝者という方法と、前者に付け加えて全部置けた時にボーナス得点が入るような方法があります。今回は前者の方法を採用しました。

モンテカルロ木探索

囲碁などによく使われるゲーム木探索のアルゴリズムであるモンテカルロ木探索について軽く説明します。

ある程度までゲーム木を展開したら、残りは終局までランダムに手を打って(これをプレイアウトと呼ぶ)勝率を計算し、最善手を近似的に導き出す探索方法を原始モンテカルロ法と言います。この探索方法は囲碁と相性がいいです。理由は2つあって、

  • ランダムに打っても必ず終局を迎える。(将棋などはランダムに打っても終局に向かう確率はほぼ0に等しい)
  • 囲碁の評価関数を作るのは困難である。

評価関数とは、盤面が自分にとってどれくらい有利な状況であるかを評価する関数です。ミニマックス法などの有名な戦略アルゴリズムに必要となります。囲碁は他のゲームと違って優劣を決める決定的ものが全くありません。プロでさえ経験から生まれる第六感で判断するほどです。それに対し、原始モンテカルロ法は終局で勝ち負けを判断するだけでいいので、評価関数が不要です。

ただ当然この原始モンテカルロ法は弱いです。特にハメ技に弱い。多くの場合有利な状況になるが、相手がうまい手を打てば一気に不利になるといった状況で逆転されやすいのです。それを防ぐにはより深くゲーム木を展開できるようにする必要があります。

この欠点を解消したのがモンテカルロ木探索です。説明は下記のページに任せます。簡単に言えば、有望な手(=暫定の勝率が高い手)を優先的に深読みしつつ、まだ未探索の手もバランスよく探索する仕組みになっています。時間をかければいつか最善手に収束するらしいです。今回は、ゲーム木を展開する優先度としてUCB値を利用したUCTアルゴリズムを使うことにします。

参考にしたページ:
http://repository.essex.ac.uk/4117/1/MCTS-Survey.pdf
http://ustimaw.hatenablog.com/entry/2014/12/14/081423
モンテカルロ木探索についての文献をいくつか見ましたが、文献によって言ってることが違って非常に困りました。最終的に上の2つを参考に実装することにしました。)

モンテカルロ木探索のBlokusへの適応

Blokus囲碁は共通点が多いです。

  • 陣取りゲームである。
  • ランダムに手を打って終局に向かう。
  • 評価関数を作るのが難しい。
  • 将棋のように「勝ち筋」を見極めるよりかは、大局的な判断が重要になる。

なのでBlokusにもモンテカルロ木探索が有効なのではないかと考えました。

問題点は、Blokusが4人対戦ゲームであることです。これは、1位を1.0勝、2位を0.75勝、3位を0.5勝、4位を0.25勝として扱うことで対応しました。

実装

プログラミング言語C++を採用しました。

Blokusの対戦AIを実装するにあたって難点が一つあって、Blokusは合法手(ゲーム違反にならない手)を列挙するのが大変です。なぜかというと、まず合法手の数が多い。そして、2つ目は合法手を列挙する処理に工夫が必要なことです。

事前にテストした結果、一番最初のプレイヤーの合法手は232通り、ゲーム序盤〜中盤には合法手は800手を超えてくるようです。囲碁は盤面の大きさから高々400通り、将棋は最大593通りです。単純計算では、多いときは2手先を全て読むのに640000通り、3手先は512000000通りの盤面を展開しなければならないわけです。

囲碁の合法手の列挙は楽です。ルール違反のマスを除いて、空いているマスのほとんどの場所に駒が置けます。それに対して、Blokusの合法手の条件は些か複雑です。ピースの種類と向き、そしてピースを置く位置によってピースの置き方を区別するとおよそ15万通りあるため、ナイーブに実装すると時間がかかりすぎます。

なので、今回は合法手の列挙の処理の記述が作業の大部分でした。約0.1msで全手列挙できるまで削りました。ゲーム開始から終局までのプレイアウトは約6msです。時間があればもう少し高速化したいです。

ソースコードは後日一週間以内にアップします。

結果

AIの思考時間は15秒以内に絞りました。
単純に僕がクソ弱いのもありますが、僕が先手でも最下位になってしまうくらいには強かったです。2位は一度だけとれました。1位は…いつかとりたいです。

f:id:uchifuji:20170915012122p:plainf:id:uchifuji:20170915012118p:plain(赤が僕)

ただゲーム序盤はやはり弱いです。合法手が多いからでしょうか。UCB値の定数の設定にもよりますが、どうやら最大で3手先くらいまでしかゲーム木を展開していないようです。

なので今後の課題は、序盤の定石を学習させることでしょうか。機械学習のこと全然わからないので、実現はまだまだ先になりそうです。

映画で泣く方法

皆さんは子供の頃、(例えば口喧嘩とかして)怒りながら泣いてしまった経験はないでしょうか?僕はあります。全然悲しくないのに。割とそういう経験ある人はいるみたいです。

最近まで人は悲しいから泣くのだと、なんとなく思ってましたが、どうやら違うようです。単純な話、感情が高ぶった時に涙が溢れてくる。その原因は、悲しみでも、怒りでも、喜びでも、虚しさでも関係ないのですね。考えてみれば、「嬉し泣き」って言葉があるくらいだから至極当たり前です。

ところで、記憶のある限り、僕は映画とかドラマで泣いたことがありません。まあ両者とも元々あまり見ないということは置いといて、最近「泣ける!!感動作!!」の触れ込みで見に行った映画も泣けませんでした。ええ!!あれほど泣けるって評判だったのに!!!って、逆に泣けない自分が不安になるほど。あ、秒速5cmはとても面白かったです。

なんか巷ではやる「泣ける!!!」的な映画って、大半が恋愛がらみな気がします。で、大抵「めちゃ共感した笑!!」みたいな感想なんですけど、「共感した」ってつまり、過去か現在進行形で恋人がいたり、または好きな人がいたりみたいなことが前提になってるんですよね。なんなんですか!!!「少子高齢化が深刻!!若者が恋愛しない!」とか言っときながら結局は恋愛至上主義なんじゃないか!!!!!!

大衆ってのは結構想像力に乏しくて、結局自分が経験した感情につながりのあるものしか想像することができないんだと思います。恋人が死ぬというシーンでも、恋人がいた経験のある人とそうでない人では悲しみの度合いが違うはず。逆に言えば、泣けない人ってのは恋愛経験に乏しい人ってことになりますね。はて誰のことでしょう?

泣ける映画、もっと大きなくくりで言えば面白い物語を作るのって、多くの人が経験したことのある強い感情を想起させるようなシナリオにすれば間違い無いのではないでしょうか。

僕の場合、合格発表の動画を見るとジーンってきます。僕以外にも、人生でおそらく一番努力するであろう受験勉強に思い入れがある人も多いはず。なんというか、合格発表動画の詰め合わせみたいな動画ないですかね。それこそ理屈抜きで泣けると思います。

あと先生や上司に怒られてすごく悔しい経験をした人も多いと思います。そういう上司を見返す映画があったら売れそうな気がします。あ、でもそう言えばそんなドラマありましたね。

思うんですけど、もらい泣きって、一番泣かせ力が高い気がします。一番感情移入がしやすい。ど直球で「泣くほど悲しいのだ」と伝えてくるから、逃れようがありません。実際、僕は人が泣くのを見てもらい泣きした経験が多いです。バライティ番組で感動話のあとタレントがみんな泣いてるのも、まぁきっとそういうことなんでしょう。

やっぱ感情移入って大事ですよねー。年をとるにつれ苦手になっていった気がします。といより、安い感動話とかを純粋に楽しむ心が失われつつある。小学生くらいまでは心霊番組を本気で怖がってたし、中学生くらいまでは、インターネットに乗ってた感動話を友達と読みあったりしてた覚えがあります。しかし、そういった話を楽しむのには余計なことを知りすぎました。もう手遅れ。というかインターネット全体にも同じような変化が起きている気がします。10年くらい前までは感動系のSSやコピペがたくさん転がっていたのに、最近めっきり目にしません。これは皆のネットリテラシーが高まったと喜ぶべきことなのか、はたまた。。。

でも結局、映画で泣ける人ってのはそういった雑念抜きで映画を楽しめる人なんでしょう。それって本当に素晴らしいことだと思います。だってそっちの方が絶対人生楽しいでしょ。

でも卑屈なのって、僕は別にマイナス要素ではないと思うんですよ。むしろパワー。日本文学なんか卑屈さの権化じゃないですか(偏見)。それに卑屈さは時に笑いを生む。逆に卑屈さゼロの太陽100%みたいな人って、付き合ってても疲れそうだし。太宰治が日本を代表する文豪になり、ちびまる子ちゃんが国民的アニメになったのも、日本人のほとんどが卑屈さを持っている確かな証拠だし、それをエンターテインメントとして活用しているのです。

なので僕は、卑屈さを持ちながらも映画で泣けるような人になりたい。

とにかく新しい来年の目標ができました。来年は映画で見事泣いてみせましょう。

僕は耳と目を閉じ口をつぐんだ人間になろうと考えた

今日は攻殻機動隊のおはなしです。まだテレビシリーズしか見ていませんが、何回でも見返したくなるカッコよさ。

知ったきっかけは、S.A.C. 2nd GIGの劇中歌の「トルキア」でした。荘厳な英語とロシア語のボーカル、現代的なシンセサイザーの音作り、それでいてエスニックな曲調。たまらんです。

攻殻機動隊の劇中歌はどれも素晴らしいですよね。もっとたくさんの人に知ってもらいたいです。

そういうわけで、僕のお気に入りをいくつかあげようと思います。

www.youtube.com
みんな大好きトルキア。一番のお気に入りです。

www.youtube.com
あえてアニメシーンの動画にしました。サイトーさん渋すぎますよね。流れるシーンも含めて好きな曲です。

www.youtube.com
S.A.C.のオープニング曲ですね。サビ前の不穏な感じが心をくすぐりますね。

www.youtube.com
言わずと知れた名曲。アニメシリーズを見終わった前と後とで全く印象が違う曲でした。見終わる前はいたって普通の曲って印象でしたが、見終わった後はより孤独感とか終末感を感じますね。

やはり見れば見るほど完璧なアニメだと思います。話の展開やシーンとBGMの絡み合いが絶妙。一話完結のストーリーは初見でも理解できるので、一周目でも内容が訳分からなさすぎてつまらなくなるってことがありませんでしたね。ミーハーでも楽しめるように気遣いができています。

そういうわけで、是非アニメみてくださいな。

伊那谷探訪

前期の期末試験が終わった。体力の消費と体の痛みがひどい。二度とやりたくない。

ところで、この度7/28-7/30の間、地理レポート資料集めのために長野県は伊那谷を訪れた。その記録をここに残そうと思う。

1日目

テストが終わった。うーれしー!!!

その足でまずは京都駅に向かう。伊那市にいい感じのゲストハウスが空いているみたいなので予約を入れた。一泊3500円というのは貧乏学生にはとても嬉しい。

バスは16:10に京都駅を発車して、東名高速に入る。そして名古屋からは中央自動車道で長野方面へ。中央道西春近バス停に到着したのは21時前。

バスを降りると道路以外周りに一切明かりがない。いや、覚悟はしていたが本当に何もない。目が慣れるのにしばらく時間がかかる。一緒に降りた一人の女性はあっという間に暗い坂道を降りていってしまった。当たり前である。ここで変質者に襲われたら生きては帰れない自信がある。

幸いなことに僕はそれほど暗闇が苦手でないから、のんびりと坂を降りることにする。非日常感と謎の高揚感と少しの不安感が入り混じった不思議な気分だった。

高速道路は伊那谷の中でも山に近い高台を走っているから伊那の夜景が一望できた。この高揚感はおそらくこれが原因であろう。写真に収めることができなかったのが残念である。今度はきちんと三脚を持って行くことにする。

f:id:uchifuji:20170802011617j:plain

坂道を降りて行くと直に崖にさしかかる。これは伊那谷の特徴的な地形で、田切地形という。谷の真ん中を流れる天竜川の河岸段丘だけでなく、扇状地を流れる支流にも侵食が進み、段丘崖が現れている。そのため、この地域には太田切川、中田切川、与田切川など、「田切」の名がつく天竜川の支流がいくつか見られる。

5kmほどの道のりを歩いて、ようやく今日の宿の赤石商店に到着した。

f:id:uchifuji:20170802012648j:plain
(7/30の朝に撮影)

古民家を改装したらしい。寝床はこんな感じのドミトリー。

f:id:uchifuji:20170802013202j:plain

店主は優しい人だった。「西春近から歩いてきた」と言うと、「外国人か」と苦笑いされてしまった。

明日は絶起するわけにはいかないから、早めに寝ることにする。

2日目

7時に起きた。この時間に起きたのは何ヶ月ぶりだろうか。

自転車をレンタルできるということなので喜んで使わせてもらうことにする。

自転車は天竜川を渡り、伊那市街へ入る。伊那の町は時代から取り残されたような町だった。最盛期は商店街は人で溢れ、多くの人が行き来していたのだろう。そういった名残を感じさせる街だった。またいつか時間をかけて散策してみたい。

f:id:uchifuji:20170807221550j:plain

伊那の町を抜けて程なく、天竜川の段丘崖に差し掛かる。その高さは優に50mはある。崖を登りきると、そこには広大な田園地帯が広がっていた。

f:id:uchifuji:20170804143427j:plainf:id:uchifuji:20170804143456j:plain
奥には伊那の町が見える。

このような広大な台地の上での水田耕作を可能にしたのは、ご存知の通り西天竜幹線水路である。

西天竜幹線水路は長野県の上伊那地方の天竜川西岸を流れる農業用水である。長野県岡谷市で天竜川から取水し、辰野町箕輪町南箕輪村伊那市の台地状の扇状地上を横断する。この一大事業によって、江戸時代に比べて石高が3倍に増えたらしい。

箕輪町郷土博物館でいただいた資料から引用して、西天竜幹線水路の概要とその歴史を記しておく。

・西天竜幹線水路の概要(西天竜用水路 箕輪町見学会資料 信州大学農学部 内川義行 から一部引用)
岡谷市川岸で取水、伊那市小沢側へ放流(流量5.56m^3/sec)
水路総延長 約26km
受益農地面積 1,180ha
事業総額 680余万円
・構造物(同上資料とpixiv百科事典から一部引用)
頭首工 1ヵ所
サイホン6ヵ所
土砂吐 7ヵ所
ゲート 2ヵ所
分水工 83ヵ所(現在) 内、円筒分水工は48ヶ所
水路橋 7ヵ所?
ずい道 不明
発電所 1ヵ所 発電所から2.5キロメートル程上流にいった小沢川の途中に助水工がある。
助水工 不明
[深沢水路橋]
箕輪町深沢川を渡る鉄筋コンクリートラーメン構造の水路橋。
現在は廃止、道路に転用。水路は逆サイフォン形式に置き換えられた。
橋長 145m 幅 3m 橋脚高 15m
[円筒分水工](西天竜幹線水路 箕輪町見学会資料 信州大学農学部 内川義行 から一部引用)
開発は可知貫一(京都帝国大学)。穂坂申彦が導入。

形状 箇所数
27
20
矩形+半円水槽 1
48
分岐数 箇所数
1 4
2 19
3 20
4 4
不明 1

・西天竜幹線水路の歴史(西天竜用水と開田 より一部引用)
大正7年(1918年)
この年起こった米騒動第一次世界大戦により、政府が食糧増産政策をとったため、開発の機運が高まった。政府が開墾費用の40%を補助し、急速に実現の運びとなった。これ以前にも明治時代にも3回開発の計画や測量が行われたが、いずれも工事着手には至らなかった。
大正8年(1919年)
5月から全地区の測量が行われ、9月に完了。また先に申請した西天竜耕地整理組合が県知事より認可され、12月4日に創立総会が開かれた。
大正10年(1921年)
用地買収開始
大正11年(1922年)
11月に起工式を挙行し、水路工事着手
昭和3年(1928年)
第5期工事までが竣工し、11月に通水式を挙行した。これに先行し、2月からは開田工事が始まっている。
昭和4年(1929年)
6月に頭首工が完成
この頃に水争いが発生し、組合当局に改善要求
昭和8年(1933年)
円筒分水の工事に着手
昭和13年(1938年)
3月、深沢サイフォン工事が竣工
昭和14年(1939年)
4月に開田工事が竣工
昭和15年(1940年)
4月28日、竣工祝賀式を盛大に挙行

工事着手から18年、悲願であった疏水工事が終結し、約1,200ha(サッカーグラウンド1700枚分)が青々とした田園地帯と化したのであった。

全国各地の用水路の中でもこの西天竜幹線水路が異色な理由は、他でもなく48基の円筒分水工群である。円筒分水の仕組みに関しては、次のサイトに説明を任せることにする。
円筒分水ドット・コム
綺麗な円形をした分水槽、メカニックな機能美、そしてそれに見え隠れする血に彩られた水争いの歴史。なかなか興味深いものではなかろうか。

今回は伊那市南箕輪村箕輪町区間の水路沿いを輪行し、円筒分水の写真を撮ってきた。その中でも気に入った分水の写真をいくつかあげる。

f:id:uchifuji:20170808154654j:plainf:id:uchifuji:20170808154604j:plainf:id:uchifuji:20170808154434j:plainf:id:uchifuji:20170808155140j:plain

円筒分水や水路はよく手入れされている。一部の円筒分水のコンクリートはピカピカだった。全国的に円筒分水がその役目を終えていっている中、こうして持続的な改修がされているのは貴重である。

次に向かったのは箕輪町郷土博物館である。ここでは上伊那地方の水事情について貴重なお話を聞くことができた。

f:id:uchifuji:20170808155646j:plain

昔から上伊那地方の段丘面を水田開発しようという構想はあったらしい。昔の人も勿体無いと感じていたのだろう。しかし、それの実現には近代的な用水技術の出現を待たなければいけなかった。

近代化以前にも、用水路の建設によって開田された地域は僅かながら存在した。山奥の小さな沢から取水し、樋を伝って村まで水を引いたり、カナートのような横井戸を掘って地下水を集めたりなど。それほどまでして水を得る必要があったのだ。

また、木曽山脈にかかる伊那市の権兵衛峠には、かつてその峠を越えた用水路が存在した。木曽山用水といい、日本海側に注ぐ奈良井川の支流、白川の上流部から取水し、等高線に沿って、僅かな下り勾配を作りながら権兵衛峠を越え、太平洋側である伊那谷へ導水していたという。この用水路は、なんと中央分水嶺を越えていたのだ。水に対する執念をひしひしと感じる。

郷土博物館の職員の方にお礼を言い、その場を後にする。次は県道88号線を南下し、駒ヶ根市へ向かう。

小沢川の田切地形によって道が分断されていて辛い。川を横切るためだけに、40mもアップダウンしなければならないのだ。隣を走る中央自動車道の橋梁を横目に見ながら坂を登る。

f:id:uchifuji:20170808161619j:plainf:id:uchifuji:20170808161730j:plain

2時間ほどかかって、駒ヶ根市に到着。駒ケ根名物ソースカツ丼を食べることにする。明治亭本店に行ったが、1時すぎにも関わらず大行列ができていたので素直に諦めた。

代わりに駒ケ根駅前の「きよし」さんでカツ丼(上)をいただく。1080円。甘いソースとサクサクの衣の相性が絶妙だった。上を頼んだだけあって、肉も柔らかく美味しい。

f:id:uchifuji:20170808162355j:plain

午後は雨予報のため、国道153号線で赤石商店へ帰ることにする。

f:id:uchifuji:20170808162726p:plain

雨に降られながらも午後3時ごろに赤石商店に到着。店主にまた「外国人か」と苦笑いされてしまう。

なんでも、この辺りでは自転車に乗る人がいないらしい。言われてみれば、自転車に乗ったおばちゃん1人と、チャリダー1人しか見かけなかった。完全に車社会。近くのコンビニでさえも車で行くらしい。
歩いている人はもっと少ない。登校中の中学生しか見かけなかった。店主は、「歩いていると珍しいから見られるんだよねー」と言っていた。

晩御飯は、赤石商店を間借りして営業していたカレー屋さんでカレーをいただく。インディカ米とジャポニカ米のミックスで、インディカ米が苦手な僕でも食べやすい。
f:id:uchifuji:20170808163733j:plain

3日目

今日は帰るだけの日程だ。

6:53、伊那市発。天竜峡で乗り換えて、中部天竜へ向かう。天竜峡をすぎると、その先は秘境駅が連続する。誰が使っているのだろうか?駅への道が獣道だけしかないような駅がゴロゴロある。

f:id:uchifuji:20170808204319j:plain

昨日の雨のせいもあり、天竜川は濁っていた。

10:42、中部天竜着。佐久間ダム発電所が見える。次の列車まで1時間ほどあるからここで昼食をとる。が、駅前を見ると全くと言っていいほど活気がない。嫌な予感がして、駅員に食堂があるかどうか尋ねる。
「日曜日だから、空いてるのは病院の売店くらいですかねー。あとはやってるかどうかわかんないけど、食堂のいどばたさんが病院の隣にあるよ。」
いや、旅行中の昼食が病院の売店だなんて旅情がなさすぎる。食わない方がマシなくらいだ。いどばたに一縷の望みを託す。

f:id:uchifuji:20170808204203j:plain

橋を渡って対岸へゆく。上流では濁っていた天竜川も、ここでは透明さを取り戻しているようだ。

f:id:uchifuji:20170808204531j:plain

幸いにも営業していた。手打ちかけそば(700円)を食らう。

f:id:uchifuji:20170808204722j:plain

「アワビカレー」と書かれた広告が目に入る。山の中でアワビとはこれいかに。

話を聞くと、廃校になった小学校の跡地を、アワビの養殖場に仕立て上げたという。いわゆる町おこしってやつだ。まだアワビがなかなか育たないので、月に2日間しか提供できないらしい。残念なことに訪れたのが第4日曜日だったからありつけなかった。今度中部天竜を訪れた時は、もっとアワビが育つようになっていると良いのだが…

11:36、中部天竜発。豊橋へ向かう。
13:23、豊橋着。抹茶あんまきを食らう。
13:33、豊橋発。東海道本線を乗り継いで京都まで帰る。書くことがない。
京都に着いたのは16:42。9時間42分の長旅だった。

旅を終えて

良質なレポート資料が手に入ったのはもちろん、段丘崖の高さや開田された水田地帯の広大さを肌で感じることができ、レポートを書く上で非常に参考になった。

あと人生初のゲストハウスを体験できて非常に満足。赤石商店には、機会があればまた訪れたいものだ。

switch vs ハッシュマップ+関数オブジェクト

前々からちょっと気になってたやつです。

switch case 文で分岐するのと、switchの式をキーにしてハッシュマップに関数オブジェクトを突っ込んだものとどちらが速いかか調べました。

検証方法

16の分岐を持つ処理を100,000,000回繰り返して、それにかかった時間の平均を取る。岐条件の定数式は整数型とする。言語はC++

分岐数を16にしたのは、それ以上の分岐数になることが滅多にないからです。

定数式の値がバラバラでない時

定数式の値があまりバラバラでない時(例えば 1, 2, 4, 5, 8)、switch case 文に対してコンパイラはジャンプテーブルと呼ばれるアドレス表を生成します。なのでswitchを使ってもハッシュマップを使っても定数時間で分岐します。ただハッシュマップはハッシュテーブルを経由する為、switchと比べて無駄が多いです。

ソースコード

#include <iostream>
#include <functional>
#include <chrono>
#include <unordered_map>

int main(int argc, char* argv[])
{
	int i;
	std::cin >> i;

	auto start = std::chrono::system_clock::now();

	for(int j = 0; j < 100000000; j++)
	{
		switch(i)
		{
			case 0:
				i = 1;
				break;
			case 1:
				i = 2;
				break;
			case 2:
				i = 3;
				break;
			case 3:
				i = 4;
				break;
			case 4:
				i = 5;
				break;
			case 5:
				i = 6;
				break;
			case 6:
				i = 7;
				break;
			case 7:
				i = 8;
				break;
			case 8:
				i = 9;
				break;
			case 9:
				i = 10;
				break;
			case 10:
				i = 11;
				break;
			case 11:
				i = 12;
				break;
			case 12:
				i = 13;
				break;
			case 13:
				i = 14;
				break;
			case 14:
				i = 15;
				break;
			case 15:
				i = 0;
				break;
		}
	}

	auto end = std::chrono::system_clock::now();
	auto msec = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();

	std::cout << msec << std::endl;

	std::unordered_map<int, std::function<void()>> func_table;
	func_table.insert(std::make_pair(0, [&i](){ i = 1; }));
	func_table.insert(std::make_pair(1, [&i](){ i = 2; }));
	func_table.insert(std::make_pair(2, [&i](){ i = 3; }));
	func_table.insert(std::make_pair(3, [&i](){ i = 4; }));
	func_table.insert(std::make_pair(4, [&i](){ i = 5; }));
	func_table.insert(std::make_pair(5, [&i](){ i = 6; }));
	func_table.insert(std::make_pair(6, [&i](){ i = 7; }));
	func_table.insert(std::make_pair(7, [&i](){ i = 8; }));
	func_table.insert(std::make_pair(8, [&i](){ i = 9; }));
	func_table.insert(std::make_pair(9, [&i](){ i = 10; }));
	func_table.insert(std::make_pair(10, [&i](){ i = 11; }));
	func_table.insert(std::make_pair(11, [&i](){ i = 12; }));
	func_table.insert(std::make_pair(12, [&i](){ i = 13; }));
	func_table.insert(std::make_pair(13, [&i](){ i = 14; }));
	func_table.insert(std::make_pair(14, [&i](){ i = 15; }));
	func_table.insert(std::make_pair(15, [&i](){ i = 0; }));

	start = std::chrono::system_clock::now();

	for(int j = 0; j < 100000000; j++)
	{
		func_table[i]();
	}

	end = std::chrono::system_clock::now();

	msec = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
	std::cout << msec << std::endl;

	return 0;
}

コンパイル

clang++ switch.cpp --std=c++11 -o switch.out -O2

結果

8回測った平均です。

switch case 178.5ms
ハッシュマップ 1146.375ms

圧倒的にswitchが速い!!!!

定数式の値がバラバラの時

このような時、switch case 文に対してコンパイラは一部で二分探索をするアセンブリコードを生成します。

ソースコード

#include <iostream>
#include <functional>
#include <chrono>
#include <unordered_map>

int main(int argc, char* argv[])
{
	int i;
	std::cin >> i;

	auto start = std::chrono::system_clock::now();

	for(int j = 0; j < 100000000; j++)
	{
		switch(i)
		{
			case 1:
				i = 2;
				break;
			case 2:
				i = 4;
				break;
			case 4:
				i = 8;
				break;
			case 8:
				i = 16;
				break;
			case 16:
				i = 32;
				break;
			case 32:
				i = 64;
			case 64:
				i = 128;
				break;
			case 128:
				i = 256;
				break;
			case 256:
				i = 512;
				break;
			case 512:
				i = 1024;
				break;
			case 1024:
				i = 2048;
				break;
			case 2048:
				i = 4096;
				break;
			case 4096:
				i = 8192;
				break;
			case 8192:
				i = 16384;
				break;
			case 16384:
				i = 32768;
				break;
			case 32768:
				i = 1;
				break;
		}
	}

	auto end = std::chrono::system_clock::now();
	auto msec = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();

	std::cout << msec << std::endl;

	std::unordered_map<int, std::function<void()>> func_table;
	func_table.insert(std::make_pair(1, [&i](){ i = 2; }));
	func_table.insert(std::make_pair(2, [&i](){ i = 4; }));
	func_table.insert(std::make_pair(4, [&i](){ i = 8; }));
	func_table.insert(std::make_pair(8, [&i](){ i = 16; }));
	func_table.insert(std::make_pair(16, [&i](){ i = 32; }));
	func_table.insert(std::make_pair(32, [&i](){ i = 64; }));
	func_table.insert(std::make_pair(64, [&i](){ i = 128; }));
	func_table.insert(std::make_pair(128, [&i](){ i = 256; }));
	func_table.insert(std::make_pair(256, [&i](){ i = 512; }));
	func_table.insert(std::make_pair(512, [&i](){ i = 1024; }));
	func_table.insert(std::make_pair(1024, [&i](){ i = 2048; }));
	func_table.insert(std::make_pair(2048, [&i](){ i = 4096; }));
	func_table.insert(std::make_pair(4096, [&i](){ i = 8192; }));
	func_table.insert(std::make_pair(8192, [&i](){ i = 16384; }));
	func_table.insert(std::make_pair(16384, [&i](){ i = 32768; }));
	func_table.insert(std::make_pair(32768, [&i](){ i = 1; }));

	start = std::chrono::system_clock::now();

	for(int j = 0; j < 100000000; j++)
	{
		func_table[i]();
	}

	end = std::chrono::system_clock::now();

	msec = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
	std::cout << msec << std::endl;

	return 0;
}

コンパイル

clang++ switch.cpp --std=c++11 -o switch.out -O2

結果

8回計測して平均

switch case 249.5ms
ハッシュマップ 2065.25ms

当たり前ですが、さっきとあまり変わりませんね。二分探索のオーダーはO(log(n))なので、もっと分岐数が多ければ差は縮まるかもしれませんが、そんなに多くの分岐を使うことがまずないですね。

結論

switch case でいいやん。