JR・私鉄全線完乗しました
お久しぶりです。公共交通機関を使おう(@yasuand)です。 本日2月23日、JR吾妻線の長野原草津口駅において、JRや私鉄各線をひっくるめた日本の鉄道の全線乗りつぶしを達成しました*1*2。 完乗達成者はそう珍しいものではないと思いますが、自身のオタク活動の区切りとして、簡単に経緯や感想を記録しておくことにします。
乗りつぶしを始めた時期
はっきりとは覚えていませんが、おそらく大学に入学した2011年からだと思います。 そこから数えて14〜15年経ってようやく達成したことになりますが、乗りつぶしのためにあちこち行くようにしたのは昨年2025年の間くらいで、基本的には旅行先を決める際にこれまで行ったことがない場所から選ぶ参考材料とするくらいでした。 比較的積極的に乗りつぶしをした2025年ですら、体力を消耗しないように遠征は最大でも隔週程度に抑える制約をつけた*3にもかかわらず4月からの9ヶ月間くらいで残った未乗区間(JR20%、私鉄50%)をほぼ消化できたくらいのペースなので、ちゃんとRTAすれば全線完乗はもっともっと短期間できることでしょう。
乗りつぶしを始めたきっかけ
15年も前のことなので覚えていません。 乗りつぶしオンライン (https://www.noritsubushi.org/) の存在を知って、「自由にあちこち行けるようになったのでせっかくだから日本全国制覇したい」と触発された、くらいの理由じゃないでしょうか?
最後に吾妻線を選んだ理由
別に思い入れがあったわけでもなく、特に深い理由はありません。 近隣で未乗のまま残っていた上越線(越後湯沢〜ガーラ湯沢)が冬季のみの運行であるため、ガーラ湯沢方面の乗りつぶしと同時に乗車しようとすると必然的にこの時期になるということと、最後をJRで締めるとJRの乗りつぶしと全鉄道の乗りつぶしを同時に達成できて気持ちよさそうに思えたことくらいです。
なお、乗りつぶしを達成した駅が吾妻線の終点である大前駅ではなく長野原草津口駅なのは、一度大前駅まで乗車したあと2014年に岩島駅〜長野原草津口駅が新線に切り替えられたことによるものです。
達成した瞬間の感想
長野原草津口駅にあまり長い時間滞在できず、草津温泉行きのバスにすぐ乗り換えてしまったので、特に思いにふけることなくバスの車窓を眺める方に思考が移ってしまいました。
また、乗りつぶしオンラインに乗車記録を入力してJRの乗車率が100%ぴったりになったのは気持ちよかったですが、私鉄線は災害による未乗区間があるのでこちらはサイトでの記録が100%にならない、というのがモヤモヤするなあというところです。
乗りつぶしをして変わったこと
去年集中的に様々な路線に乗ったので、記憶が比較的新鮮なうちに路線を乗り比べることになり、路線の特徴を掴むために見るべきポイントのようなものがなんとなくわかるようになってきた気がします。
しかし、逆に列車に乗るとそのポイントを探しにいくようになってしまったので、ただぼっと景色を眺めて時間を過ごして休むような乗り方はできなくなったなあと感じます。 心を無にするために飯田線乗り通しを昔ちょこちょこやっていましたが、別の方法を探す必要がありそうですね。
また、これまで旅行先の選択肢が乗りつぶしマップの未乗区間のある場所に縛られるような感覚がありましたが、今日を境に消えてくれるのでしょうか?
完乗の次にやること
特に決めていませんが、自分の中でのオタク趣味の方向性が変わってきていることもあり、乗りつぶし第2弾のようなことは当分しないと思います。 別のオタク趣味に打ち込みたい気持ちもあるし、人生を進めたい気持ちもあるし、この先の方向性はかなり揺れ動いております。。。
*1:対象となる路線は、乗りつぶしオンライン を基準とします。
*2:2026年2月23日現在、災害等による長期運休中の路線を除きます。
*3:とは言っても、金曜日の退勤後夜行バスに乗り込んで遠征先に向かい、乗りつぶしを兼ねて月曜の朝遠征先から出勤するということはしていたので、制約に意味があったかというと…?
SNSアカウント置き場
お久しぶりです。やっさん(公共交通機関を使おう)です。
ここ最近ずっとインターネット上での活動が低調でしたが、きちんと健康に*1生存しています。
特にこのブログは8年ぶりの更新のようで、こんなにも長い間休眠していたにもかかわらず何事もなかったかのようにログインできたことは驚きです。
さて、この8年の間Twitterにはちょくちょく顔を覗かせていたのですが、Twitterブランドも青い鳥もなくなるという話が出ていて、このコミュニティはどうなるのか不安な気持ちでいます。
そういえばSNSアカウントの一覧をまとめた記事は今まで書いていなかったので、この機会にまとめようと思ったのがこの記事です。
Twitter以外も覗いてみていただけると嬉しいですし、引き続き交流してもいいという方はフォローしていただけると泣いて喜びます。
*1:要出典
JRの乗車券の有効期間と途中下車
お久しぶりです。
今回は、途中下車制度を使ってJRでお得に旅行しようというお話です。
下のリンクの記事に触発されて書きました。
sow-te.hatenablog.com
途中下車ができる私鉄線は少ない、私鉄線は距離が短いので途中下車する必要性があまりない、などの理由により、この記事では基本的にJRに的を絞って説明します。
途中下車とは
基本的にJRの普通乗車券*1は、移動の途中、乗車券の有効期間内なら何回でも、後戻りしない限り、「1回改札の外に出たあとに改札内に入り、移動を続行する」ということができます。これを途中下車といいます。
改札の外に出てからどれだけ時間が経っていても、(たとえ数日間などの長時間であっても)乗車券の有効期間内であれば再び改札の中に入って移動を続行できます。
なお、「改札の外に出る」ということを、下車といいます。乗っている電車から降りただけでは下車にはなりません。
たまに「電車乗ってたらお腹すいたから途中下車して改札の中でそば食べてまた電車乗った」などと言う人がいますが、厳密には誤りです。
また、回数券や特急券、そしてSuicaやPASMO、ICOCAなどのIC乗車券は、途中下車できません。
JRの普通乗車券であっても途中下車できない条件がいくつもあるのですが、それはあとで説明します。
途中下車できない乗車券の場合、切符に「下車前途無効」などと書いてあるので、それで判別することができます。
途中下車のしかた
基本的に自動改札に通せば途中下車の処理をしてくれます。
私は途中下車したときに押してもらえる駅名のはんこを押してほしく基本的に有人改札を使うのであまりわからないのですが、一部地域によっては自動改札が途中下車に対応していないこともあるようです。
途中下車しようとして切符が自動改札機に回収されてしまった場合は、駅員に言えば取り出してもらえるはずです。もしくは不安なら私のように有人改札を使うようにすることです。
途中下車を活用して得する例
東京から大阪の実家に帰省するのに、行きの途中で名古屋の親戚の家に立ち寄る用事ができたとします。
普通は新幹線を使うと思いますが、新幹線に乗るために必要な特急券は前述したように途中下車できないので、乗車券で途中下車制度を活用しようがしまいが、特急券はどのみち「東京→名古屋」「名古屋→新大阪」「新大阪→東京」の3枚を買う必要があります。
したがって、以下では新幹線の特急券は無視して、純粋に乗車券部分だけを比較します。
まず、途中下車ができないと仮定すると、おそらく次の3枚の乗車券を買うことになると思います。
最初の2枚が行きのぶんで、残りが帰りのぶんです。
合計すると、18360円(学割14680円)です。
次に、途中下車制度を活用するとします。
東京都区内→大阪市内の乗車券は途中下車することができるかというと、できます。
そこで、東京都区内←→大阪市内の往復乗車券を買えばいいということがわかります。
東京都区内から大阪市内までの往復乗車券は、17500円(学割14000円)です。
また、http://sow-te.hatenablog.com/entry/2015/03/21/224223のように、大阪市内までの往復乗車券を買うのではなく、西明石駅まで伸ばした乗車券を使っても、同じことがいえます。
東京都区内から西明石駅までの往復割引乗車券は、17280円(学割13820円)です。
ということで、「東京都区内→名古屋市内」「名古屋市内→大阪市内」「大阪市内→東京都区内」の3枚の片道乗車券を購入する場合と、「東京都区内←→西明石駅」の1枚の往復割引乗車券を購入する場合とを比較して、1080円(学割使用だと860円)も安くすることができました。
安くなる理由
JRの運賃は、基本的に、乗車するキロ数にある賃率をかけたものを四捨五入するなどゴニョゴニョ加工して計算します。
ところが、この賃率は、キロ数が長くなればなるほど低くなるように設定されているので、同じキロ数乗車するなら、できるだけひとつづきの乗車券とするほうが安くなるわけです。
例えば本州のJR線の場合、300kmまで16.20円/km、301kmから600kmまで12.85円/km、601kmからは7.05円/kmとなっています。
300kmまでの賃率と601kmからの賃率を比較すると半額以下となっていて、往復割引や学割と比べてかなり強烈な割引になっていることがわかります。
安くならない例
ところが、途中下車制度を使っても安くならない例も存在します。
東京から川崎にある友人宅に立ち寄ってから、名古屋の実家に行く例を考えます。
東京駅から川崎駅までの運賃は片道310円です(これは学割は使えません)。
横浜市内(川崎駅は川崎市にありますが、JRの運賃計算上は横浜市内の駅に属します)から名古屋市内までの運賃は片道5620円(学割4490円)です。
したがって、途中下車制度を活用しない場合は、合計5930円(学割4800円)になります。
一方で、途中下車制度を活用しようとして、東京都区内から名古屋市内までの乗車券を買うと、前に出たように片道6260円(学割5000円)です。
330円(学割を使う場合は200円)も高くなってしまいました。どうしてでしょうか。
これは、東京・大阪では賃率の安い区間が設定されているということ、JRの運賃は階段状に上がっていくのである距離を超えると急に運賃がガクッと上がることがよくあること、特定都区市内制度により運賃計算に使う距離数を短くできたこと、が原因です(特定都区市内制度については後述します)。
このように、どこか目的地に向かう途中で別のどこかに立ち寄る必要があるというような場合に、途中下車制度を活用して安くなるかどうかはケース・バイ・ケースです。
しかし、途中下車を使って安くなる理由を考えればわかるように、乗車券のキロ数が300km程度までは効果が低く、400km程度を超えだすと積極的に活用したほうが良いということが一般に言えると思います。
途中下車するための条件
途中下車制度は「移動の途中でどこかに立ち寄る必要がある」というような比較的消極的な理由だけではなく、「移動の途中のあそこで見た景色が素晴らしかったから途中で降りて観光しよう」というような積極的な理由でももちろん利用することができます。
そうすると、例えばはるばる青森から観光に上京する人が、「品川まで行きたいんだけど、一旦上野で降りて動物園でパンダ見て、また乗って秋葉原で降りてオタク活動して、また乗って東京で降りて…」のようなこともしたくなってきますよね。
ところが、残念ながらこのようなことはできません。
途中下車はいつでも必ずできるのではなく、普通乗車券であっても、途中下車できない場合がいくつか定められています。
- 100km以下の乗車券は、途中下車できません。
- 東京・大阪・福岡・仙台・新潟の各大都市近郊区間内で完結する乗車券は、途中下車できません。
- 乗車券の発駅もしくは着駅が、東京都区内や東京山手線内、○○市内となっているときは、その都区内・市内の駅
- 発駅・着駅を東京都区内や東京山手線内、○○市内と表記するのは、特定都区市内制度と言います。
- 各都区・市内の駅と、その中心駅から201km以上ある駅との間の運賃は、その中心駅からのキロ数で計算します。
- 東京山手線内の駅であれば、東京駅から101km以上200km以下の駅との間であっても、東京駅からのキロ数で計算します。
- 東京都区内は東京23区内、東京山手線内は山手線と中央快速線(神田~新宿)、中央・総武緩行線(秋葉原~代々木)の駅が該当します。
- ○○市内に該当する市は、横浜・名古屋・京都・大阪・神戸・広島・北九州・福岡・仙台・札幌です。
- ○○市内になる駅は、ほぼその市の範囲に一致しますが、先ほど出てきた川崎駅の例のように一部例外があるので、Wikipedia等で確認することをおすすめします。
- 特定都区市内発着の乗車券には、「券面表示の都区内(市内)下車前途無効」などと書いてあると思います。
- 青森から上京した人が~~~の例で上野駅などで途中下車できないのは、これが理由です。
なお、新幹線は基本的に大都市近郊区間には含まれない(例外として、東海道新幹線の米原~新大阪と山陽新幹線の西明石~相生は大阪近郊区間に含まれます)ので、例えば東京から熱海に行くのに小田原で一旦下車したい、というような場合には、在来線経由ではなく新幹線経由を指定するといいです。
新幹線経由を指定していても、新幹線駅に限らず、平行する在来線の各駅で途中下車することができます。*2
乗車券の有効期間
冒頭にも書きましたが、乗車券の経路の途中で一旦下車したあと、再び改札の中に入って移動を続行するには、その乗車券の有効期間内であることが必要です。
片道乗車券の有効期間は100kmまで1日、200kmまで2日、それ以後は200kmごとに1日追加されます(したがって例えば東京から大阪までの片道乗車券は、556.4kmなので、4日間有効です)。
往復乗車券の有効期間は、片道乗車券の2倍です。
また、移動の途中で乗車券の有効期間が切れても、途中下車をしなければ着駅まで行くことができます。
できれば乗車券をひとつづきにさせたいが、自分の予定と有効期間の関係でできないというような場合に、有効期間を無理矢理伸ばす方法も存在しますが、説明が入り組みますので今回は省きます。
最後に
JRの旅客営業規則はとても複雑です。
しかし、その中にはうまく使うと、トクトクきっぷの類を使わなくてもとてもお得になるような規則も存在します。
また、無理矢理一筆書きの乗車券にしてしまうだとかの工夫の余地もたくさん存在します。
ぜひこの乗車券パズルを楽しんでみてください。
C++11で、ポインタ変数のもとの型と同じ型の変数を宣言する方法
みなさんお久しぶりです。
最近バイトとか研究とかでない個人的な開発をする余裕がなくなっていて、記事として公開できるネタが少なくてなかなか更新できていません(この状態は今後しばらく続くと思います)。
さて今回の記事は、「ポインタ変数のもとの型と同じ型の変数を宣言する方法」です。
具体的にどういうことかというと、下のようなコードを例に説明します。
int* hoge = new int[10]; // int*型の変数hoge decltype(*hoge) fuga; // fugaの型はこのままだとint&
このように単純に書いてしまうとint&型の変数が宣言されるが、やっぱりどうしてもint型の変数を宣言したい!ということです。
つまるところ、「ポインタ型からどうやってもとの型を取ってくるねん」ということになるのですが、天下のGoogleで検索すると、我らが救世主stackoverflowにほぼドンピシャな記事がありました(http://stackoverflow.com/questions/8696452/get-value-type-of-dereferencable-types)。
この記事で書かれているのは、C++のtemplate構造体を宣言して、その中でtypedefするみたいな方法です。
この方法で全然問題ないと思うんですが、この方法だと得られるのはあくまでtypedefされた型であって、もとの型ではないので、IntelliSenseで型名を表示すると汚らしい形式になって非常に気持ちが悪いです。
template<typename> struct _dereference; template<typename T> struct _dereference <T*> { typedef T type; }; int* hoge = new int[10]; _dereference<decltype(hoge)>::type fuga; // 実質int型だけどIntelliSenseの表示は // _dereference<int*>::type
なので、このstackoverflowの回答のアイデアをもとに、IntelliSenseにやさしい(?)方法をとります。
typedefしてるのがIntelliSenseが汚らしい表示をする原因なので、typedefせずにダミーメンバをdecltypeすることで解決します。
template<typename> struct _dereference; template<typename T> struct _dereference < T* > { T _dummy; }; #define dereference(T) decltype(_dereference<T>()._dummy) ... int* hoge = new int[10]; dereference(decltype(hoge)) fuga; // 正真正銘int型のfugaになった!
少し一般的に、ポインタ型をdereferenceするためのマクロという形で書いてみました。
コードの実行に使うデータ量は手でfugaの型名を直打ちするのと全く変わらないです。
生成されるコード量もたぶん変わらないでしょう。
コンパイル時間は知りませんが。
2014/07/06修正
各コードの構造体の名前を変えました。
C言語のプリプロセッサでFizzBuzz
alucky0707さんの下の2記事に触発されて(かつ参考にしながら)C言語のプリプロセッサだけでFizzBuzzを書いてみました。
インクルードファイルの階層の深さの制限とかは(事実上)なくなるようにしました。
- CPP(コンパイルしない方の関数型なC言語)プログラミング入門。とりあえずFizzBuzzまで - Qiita
- http://qiita.com/alucky0707/items/3599cdcf973382df978b
- C言語 - MSVCでもCPPでFizzBuzzしてみた - Qiita
- http://qiita.com/alucky0707/items/d4073a9a3af9a804477a
下のソースコードを普通にコンパイルしてできる実行可能ファイルを実行すると、1000までのFizzBuzzができます。
FizzBuzzの上限数を変えたい場合は、(例えば31415までやりたいとかだとすると)gccだと
$ gcc -D"FIZZBUZZ_MAX=31415" fizzbuzz.c
MSVCだと
> cl /DFIZZBUZZ_MAX=31415 fizzbuzz.c
とかでコンパイルすればいいと思います(ソースコード上は5桁までしか対応してないですが、適宜拡張すれば桁数はいくらでも増やせます)。
それではとりあえずソースコード全体です。
# ifndef __FIZZBUZZ__ # # define __FIZZBUZZ__ # # ifndef FIZZBUZZ_MAX # define FIZZBUZZ_MAX 1000 # endif # # define FIZZ_FLAG ((COUNTER % 3 == 0) && (0 < COUNTER)) # define BUZZ_FLAG ((COUNTER % 5 == 0) && (0 < COUNTER)) # # define c0 0 # define c1 0 # define c2 0 # define c3 0 # define c4 0 # # define TOSTRING_(x) #x # define TOSTRING(x) TOSTRING_(x) # define JOIN_(a, b) a##b # define JOIN(a, b) JOIN_(a, b) # # define ctr4 c4 # define ctr3 JOIN(c3, ctr4) # define ctr2 JOIN(c2, ctr3) # define ctr1 JOIN(c1, ctr2) # define ctr0 JOIN(c0, ctr1) # define COUNTER # # define DEPTH 0 # # endif # if c0 != 0 # undef COUNTER # define COUNTER ctr0 # elif c1 != 0 # undef COUNTER # define COUNTER ctr1 # elif c2 != 0 # undef COUNTER # define COUNTER ctr2 # elif c3 != 0 # undef COUNTER # define COUNTER ctr3 # else # undef COUNTER # define COUNTER ctr4 # endif # if DEPTH == 0 # #include <stdio.h> int main() { # # undef DEPTH # define DEPTH 1 # # undef c0 # define c0 0 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c0 # define c0 1 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c0 # define c0 2 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c0 # define c0 3 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c0 # define c0 4 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c0 # define c0 5 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c0 # define c0 6 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c0 # define c0 7 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c0 # define c0 8 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c0 # define c0 9 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c0 # define c0 0 # # undef DEPTH # define DEPTH 0 # return 0; } # # elif DEPTH == 1 # # undef DEPTH # define DEPTH 2 # # undef c1 # define c1 0 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c1 # define c1 1 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c1 # define c1 2 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c1 # define c1 3 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c1 # define c1 4 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c1 # define c1 5 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c1 # define c1 6 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c1 # define c1 7 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c1 # define c1 8 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c1 # define c1 9 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c1 # define c1 0 # # undef DEPTH # define DEPTH 1 # # elif DEPTH == 2 # # undef DEPTH # define DEPTH 3 # # undef c2 # define c2 0 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c2 # define c2 1 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c2 # define c2 2 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c2 # define c2 3 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c2 # define c2 4 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c2 # define c2 5 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c2 # define c2 6 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c2 # define c2 7 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c2 # define c2 8 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c2 # define c2 9 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c2 # define c2 0 # # undef DEPTH # define DEPTH 2 # # elif DEPTH == 3 # # undef DEPTH # define DEPTH 4 # # undef c3 # define c3 0 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c3 # define c3 1 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c3 # define c3 2 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c3 # define c3 3 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c3 # define c3 4 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c3 # define c3 5 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c3 # define c3 6 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c3 # define c3 7 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c3 # define c3 8 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c3 # define c3 9 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c3 # define c3 0 # # undef DEPTH # define DEPTH 3 # # elif DEPTH == 4 # # undef DEPTH # define DEPTH 5 # # undef c4 # define c4 0 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c4 # define c4 1 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c4 # define c4 2 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c4 # define c4 3 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c4 # define c4 4 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c4 # define c4 5 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c4 # define c4 6 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c4 # define c4 7 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c4 # define c4 8 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c4 # define c4 9 # if COUNTER <= FIZZBUZZ_MAX # include __FILE__ # endif # undef c4 # define c4 0 # # undef DEPTH # define DEPTH 4 # # elif DEPTH == 5 # # if FIZZ_FLAG && BUZZ_FLAG printf("FizzBuzz\n"); # elif FIZZ_FLAG printf("Fizz\n"); # elif BUZZ_FLAG printf("Buzz\n"); # elif 0 < COUNTER printf(TOSTRING(COUNTER)"\n"); # endif # # endif
以下適当に解説します。
カウンターは十進数の各桁に分割して管理します。
元のコードでは単にループして頑張って各桁の数字を書き換えるものでしたが、インクルードファイルの階層ごとに扱う桁を分けて深さ優先探索的なことをすることで事実上の制限をなくしました。
イメージ的にはこんなCのコードを実行している感じ。
fizzbuzz1()がalucky0707さんのFizzBuzzのイメージで、fizzbuzz2()が私のFizzBuzzのイメージです。
int c0 = 0; int c1 = 0; int c2 = 0; int c3 = 0; int c4 = 0; void counter(); // returns the value of the counter // alucky0707's fizzbuzz void fizzbuzz1() { if (c4 == 0) { c4 = 1; } else if (c4 == 1) { // // ... // } else if (c4 == 9) { c4 = 0; if (c3 == 0) { // // ... // } } // // write fizz buzz... // fizzbuzz1(); } // my fizzbuzz void fizzbuzz2(int depth) { if (depth == 0) { ++depth; c0 = 0; if (counter() <= FIZZBUZZ_MAX) fizzbuzz2(depth); c0 = 1; if (counter() <= FIZZBUZZ_MAX) fizzbuzz2(depth); // // ... // c0 = 9; if (counter() <= FIZZBUZZ_MAX) fizzbuzz2(depth); } else if (depth == 1) { ++depth; c1 = 0; if (counter() <= FIZZBUZZ_MAX) fizzbuzz2(depth); c1 = 1; if (counter() <= FIZZBUZZ_MAX) fizzbuzz2(depth); // // ... // c1 = 9; if (counter() <= FIZZBUZZ_MAX) fizzbuzz2(depth); } else if (depth == 2) { // // ... // } else if (depth == 5) { // // write fizz buzz... // } }
ちなみにこの方法だと、FizzBuzzできる最大数を制限するのはインクルードファイルの階層数よりももはや数値定数の最大値だとかコンパイル時間だとかそっちになってくると思います。
もっとコードを短くスマートにできるのかもしれませんが、私にはそのスキルはありませんでした。
コンパイル時実行バンザイ!
C#の2次元配列とLINQ
今日はC#の2次元配列とLINQの相性がものすごく悪いなあという話です。
途中が無駄に長いので、途中がどうでもよくて対処法だけ知りたいという方はどうしてもLINQのメソッドを使いたいの節まで飛んでください。
きっかけ
1次元配列だと
var a = new int[] { 2, 7, 1, 8, 2, 8 }; var b = new int[] { 3, 1, 4, 1, 5, 9 }; var c = a.Zip(b, (n, m) => n + m).ToArray();
みたいにSystem.Linq.Enumerable.Zip()で各要素ごとに同じ操作をすることができます。
それと同じように2次元配列でも
var a = new int[,] { { 2, 7 }, { 1, 8 }, { 2, 8 } }; var b = new int[,] { { 3, 1 }, { 4, 1 }, { 5, 9 } }; var c = a.Zip(b, (n, m) => n + m).ToArray();
のように書いて要素ごとに操作をしたいなあと思ってコードを書きましたがa.Zまで書いたところでVisualStudioのIntelliSenseに「そんな関数ねぇよw」と言われて、どうやったらこういうのを簡単に書けるんだろうと思って考えました。
「四角形配列」と「ジャグ配列」
C#で多次元配列を使うときには、大きく分けて次の2つの方法があります。
// 四角形配列の例 var ar1 = new int[,] { { 1, 2 }, { 3, 4 }, { 5, 6 } }; // ジャグ配列の例 var ar2 = new int[][] { new int[] { 1, 2 }, new int[] { 3, 4 }, new int[] { 5, 6 } };
それぞれ欠点利点がありますが、とりあえず代表的なもの簡単にまとめてみます。
| 四角形配列 | ジャグ配列 | |
|---|---|---|
| メモリ使用量 | 少ない | 多い |
| 初期化 | 簡単 | 面倒 |
| 行ごとのサイズ | 必ず一定 | 変えられる |
| foreachアクセス | コード例参照 | コード例参照 |
foreachによるアクセスですが、四角形配列では例えば
using System; class Program { public static void Main() { var ar_rect = new int[,] { { 1, 2 }, { 3, 4 }, { 5, 6 } }; foreach (var n in ar_rect) { Console.Write("{0} ", n); } } }
とすると、出力は
1 2 3 4 5 6
となり、1次元配列のようにならされて順番にアクセスすることがわかります。
一方ジャグ配列で同じことをしようとすると、
using System; class Program { public static void Main() { var ar_jag = new int[][] { new int[] { 1, 2 }, new int[] { 3, 4 }, new int[] { 5, 6 } }; foreach (var ar in ar_jag) { foreach (var n in ar) { Console.Write("{0} ", n); } } } }
というふうにループを2重に書かないといけませんね。四角形配列は単なるintの配列である一方、ジャグ配列はintの配列の配列だからです。
さて本題
四角形配列とジャグ配列にどんな差があるのか簡単にわかったところで、本題です。
四角形配列も単なる1次元配列もどちらもSystem.Arrayクラスのインスタンスのはずなのになぜ前者はZipが使えなくて後者は使えるのでしょうか。
使えないメソッドはZipだけではありません。SumやAverageなど、LINQの関数全般が使えません、というよりLINQのクエリ式自体使えません。
using System; class Program { public static void Main() { var ar = new int[,] { { 1, 2 }, { 3, 4 }, { 5, 6 } }; var sum = ar.Sum(); // コンパイルエラー var odds = from n in ar where n % 2 == 0 select n; // コンパイルエラー } }
ここでMSDNのヘルプを見ると、ZipやSumはSystem.Linq.Enumerableクラスに定義されている拡張メソッドであることがわかります。
例えばSumの例で見ると、たくさんのオーバーロードがありますが、基本となるのは
public static int Sum(this IEnumerable<int> source)
です。
要するに、Sum関数を使うには対象のオブジェクトがSystem.Collections.Generic.IEnumerable<>を実装したクラスのインスタンスである必要があります。
そこで四角形配列とジャグ配列がどんなクラスのインスタンスなのかを含め、いろいろ調べてみます。
using System; class Program { public static void Main() { var ar_rect = new int[,] { { 1, 2 }, { 3, 4 } }; var ar_jag = new int[][] { new int[] { 1, 2 }, new int[] { 3, 4 } }; var re_t = ar_rect.GetType(); var ja_t = ar_jag.GetType(); Console.WriteLine("ar_rect is Array: {0}", ar_rect is Array); Console.WriteLine("ar_jag is Array: {0}", ar_jag is Array); Console.WriteLine(); Console.WriteLine("ar_rect.GetType() = {0}", re_t); Console.WriteLine("ar_jag .GetType() = {0}", ja_t); Console.WriteLine(); Console.WriteLine("ar_rect.GetType().BaseType = {0}", re_t.BaseType); Console.WriteLine("ar_jag .GetType().BaseType = {0}", ja_t.BaseType); Console.WriteLine(); Console.WriteLine("ar_rect.GetType() == typeof(Array) ? {0}", re_t == typeof(Array)); Console.WriteLine("ar_jag .GetType() == typeof(Array) ? {0}", ja_t == typeof(Array)); Console.WriteLine(); Console.WriteLine("type of ar_rect is a implementation of: "); foreach (var t in re_t.GetInterfaces()) { Console.WriteLine(t); } Console.WriteLine(); Console.WriteLine("type of ar_jag is a implementation of: "); foreach (var t in ja_t.GetInterfaces()) { Console.WriteLine(t); } Console.WriteLine(); } }
すると、こんな結果が得られるはずです。
ar_rect is Array: True ar_jag is Array: True ar_rect.GetType() = System.Int32[,] ar_jag .GetType() = System.Int32[][] ar_rect.GetType().BaseType = System.Array ar_jag .GetType().BaseType = System.Array ar_rect.GetType() == typeof(Array) ? False ar_jag .GetType() == typeof(Array) ? False type of ar_rect is a implementation of: System.ICloneable System.Collections.IList System.Collections.ICollection System.Collections.IEnumerable System.Collections.IStructuralComparable System.Collections.IStructuralEquatable type of ar_jag is a implementation of: System.ICloneable System.Collections.IList System.Collections.ICollection System.Collections.IEnumerable System.Collections.IStructuralComparable System.Collections.IStructuralEquatable System.Collections.Generic.IList`1[System.Int32[]] System.Collections.Generic.ICollection`1[System.Int32[]] System.Collections.Generic.IEnumerable`1[System.Int32[]] System.Collections.Generic.IReadOnlyList`1[System.Int32[]] System.Collections.Generic.IReadOnlyCollection`1[System.Int32[]]
ということで、ar_rectもar_jagもBaseTypeがSystem.Arrayになっているので、上ではar_rectもar_jagもArrayクラスのインスタンスと書きましたが、実際にはその派生クラスのインスタンスのようです。
ここで、再びMSDNでArrayクラスの定義を見てみると、
[SerializableAttribute] [ComVisibleAttribute(true)] public abstract class Array : ICloneable, IList, ICollection, IEnumerable, IStructuralComparable, IStructuralEquatable
ということで、Arrayクラスは抽象クラスなのでインスタンス化できず、C#の配列の実体はArrayクラスの派生クラスのインスタンスです。これは僕の勘違いというか不勉強なだけですね。
LINQのメソッドを使うにはSystem.Collections.Generic.IEnumerable<>を実装している必要があるので、やっぱり2次元配列にはLINQのメソッドは(そのままでは)使えません。
なぜ使わせてくれないのか
CLIの2014/02/01現在の最新の仕様書(ECMA-335)の§I.8.9.1を見ると、
(前略)
Additionally, a created vector with element type T, implements the interface System.Collections.Generic.IList<U>, where U := T. (§I.8.7)
(中略)
Array types form a hierarchy, with all array types inheriting from the type System.Array. This is an abstract class (see §I.8.9.6.2) that represents all arrays regardless of the type of their elements, their rank, or their upper and lower bounds. The VES creates one array type for each distinguishable array type. In general, array types are only distinguished by the type of their elements and their rank. However, the VES treats single dimensional, zero-based arrays (also known as vectors) specially. Vectors are also distinguished by the type of their elements, but a vector is distinct from a single-dimensional array of the same element type that has a non-zero lower bound. Zero-dimensional arrays are not supported.
(後略)
http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-335.pdf
とか書かれていて、簡単に訳しますと
(前略)
加えて、要素の型をTとして生成されたベクトルは U:=T として System.Collections.Generic.IList<U> インタフェースを実装します。
(中略)
配列型は階層構造をなし、すべての配列型は System.Array 型を継承します。これは配列要素の型、次元、上限下限を無視したすべての配列をあらわす抽象クラスです。VESは各区別可能な配列の型に対して1つの配列型を作ります。一般的に、配列の型はその要素の型と次元数によってのみ区別されます。しかしながら、VESは1次元で下限が0の配列(要するにベクトル)を特別扱いします。ベクトルもまたその要素の型によって区別されますが、要素が同じ型で下限が0でない1次元配列とは全く異なります。0次元の配列はサポートされません。
(後略)
ということで*1、次のことがわかります。
- 配列については1次元配列でしかも添字が0から始まるもの(いわゆる普通の配列。配列が入れ子になったものとかもこれ)だけが特別扱い
- 普通の配列だけがSystem.Collections.Generic.IList<>を実装している
普通の配列だけが特別扱いされるというのは僕としては非常に納得しています。というのも、全種類の配列を同一視して命令セットを作る場合、多次元だったりインデックスが0から始まらなかったりして、実際のメモリ上のアドレスを計算するのにとても時間がかかってしまいそうで非効率的です。一方普通の配列の場合は、C言語とかを触った人なら当然わかると思いますが、実際のアドレスを計算するのに先頭アドレスに(要素データのバイト数)×(インデックスの値)を足すだけでいいので、アクセスがとても早くて効率的だからです*2。
一方2つ目が今回の問題の本質ですが、結局よくわかりません。System.Collections.IListは実装しているんだから、System.Collections.Generic.IList<>も実装してくれたっていいじゃんとか思うわけです。
LINQのメソッドの中にはTakeWhile<>()とかもあって、これは多次元配列でどんな動作すりゃええねんという感じで、確かに実装されちゃ困るようなメソッドもあるわけですが、たぶんLINQ登場以前からSystem.Collections.Generic.IList<>を実装していないということは変わらないわけで…
どうしてもLINQのメソッドを使いたい
標準でサポートしてくれていなくても、やっぱり多次元配列の要素の中の最大値とかは簡単に計算したいし、クエリ式を書いて簡単に要素抽出とかしたいわけです。
じゃあどうすればいいかというと、いくつか方法が考えられるので、順に紹介してみたいと思います。
多次元配列を使わない
ちょっと乱暴ですが、(インスタンス生成がめんどくさいという話はおいといて)多次元配列は1次元配列の配列の配列の…の配列という形で記述できるので、すべてそれに移行してしまうという方法です*3。記法が全体的に複雑になってわかりにくくなるという欠点もありますが、非常に明快単純な解決法です。
キャストする
ちょっと使うだけならたぶんこれが一番簡単です。System.Collections.IEnumerableにはCast<>拡張メソッドがあります。これはSystem.Collections.IEnumerableをSystem.Collections.Generic.IEnumerable<>に変換するものです。素晴らしいですね。さっそく使ってみましょう。
using System; using System.Linq; class Program { public static void Main() { var data = new int[,] { { 2, 3, 5 }, { 7, 9, 8 }, { 6, 4, 1 } }; var max = data.Cast<int>().Max(); Console.WriteLine("max = {0}", max); var even = from n in data.Cast<int>() where n % 2 == 0 select n; foreach (vat n in even) { Console.WriteLine(n); } } }
非常に簡単です。このメソッドがSystem.Linq.Enumerableに定義されているというのはつまり、LINQ使いたいときはこれ使えとMSが言ってるのだと思います。
自分で拡張メソッドを定義する
System.Linq.Enumerable.Cast<>を使うとLINQの操作ができるようになることがわかりましたが、これを使うと、得られる結果が1次元的なものになってしまうという弊害があります。Zipしたら配列の形が変わってしまったとかいうのは意味がわからないですよね。1次元配列に対しては得られる結果も1次元的なもので変わらないので、別に大丈夫なわけですが。このように柔軟な操作を必要とする場合は、自分で適当なクラスに拡張メソッドを定義する必要があります。ただし、C#ではyield-returnを使うことでLINQ式の遅延実行を行っていますが、yield-returnを使う場合は原理的に多次元的に結果を返すことができないので、遅延実行を諦めざるを得ないという面があります。
まとめ
多次元配列はクソ。ジャグ配列使え。どうしても嫌ならCast<>でIEnumerable<>に変換して汚物を消毒。
長々と読んでいただきありがとうございました。