やっさんの雑記

プログラミングでやってみたこととか

JRの乗車券の有効期間と途中下車

お久しぶりです。

今回は、途中下車制度を使ってJRでお得に旅行しようというお話です。
下のリンクの記事に触発されて書きました。
sow-te.hatenablog.com

途中下車ができる私鉄線は少ない、私鉄線は距離が短いので途中下車する必要性があまりない、などの理由により、この記事では基本的にJRに的を絞って説明します。

途中下車とは

基本的にJRの普通乗車券*1は、移動の途中、乗車券の有効期間内なら何回でも、後戻りしない限り、「1回改札の外に出たあとに改札内に入り、移動を続行する」ということができます。これを途中下車といいます。
改札の外に出てからどれだけ時間が経っていても、(たとえ数日間などの長時間であっても)乗車券の有効期間内であれば再び改札の中に入って移動を続行できます。
なお、「改札の外に出る」ということを、下車といいます。乗っている電車から降りただけでは下車にはなりません。
たまに「電車乗ってたらお腹すいたから途中下車して改札の中でそば食べてまた電車乗った」などと言う人がいますが、厳密には誤りです。

また、回数券や特急券、そしてSuicaPASMOICOCAなどのIC乗車券は、途中下車できません。
JRの普通乗車券であっても途中下車できない条件がいくつもあるのですが、それはあとで説明します。
途中下車できない乗車券の場合、切符に「下車前途無効」などと書いてあるので、それで判別することができます。

途中下車のしかた

基本的に自動改札に通せば途中下車の処理をしてくれます。
私は途中下車したときに押してもらえる駅名のはんこを押してほしく基本的に有人改札を使うのであまりわからないのですが、一部地域によっては自動改札が途中下車に対応していないこともあるようです。
途中下車しようとして切符が自動改札機に回収されてしまった場合は、駅員に言えば取り出してもらえるはずです。もしくは不安なら私のように有人改札を使うようにすることです。

途中下車を活用して得する例

東京から大阪の実家に帰省するのに、行きの途中で名古屋の親戚の家に立ち寄る用事ができたとします。

普通は新幹線を使うと思いますが、新幹線に乗るために必要な特急券は前述したように途中下車できないので、乗車券で途中下車制度を活用しようがしまいが、特急券はどのみち「東京→名古屋」「名古屋→新大阪」「新大阪→東京」の3枚を買う必要があります。
したがって、以下では新幹線の特急券は無視して、純粋に乗車券部分だけを比較します。

まず、途中下車ができないと仮定すると、おそらく次の3枚の乗車券を買うことになると思います。
最初の2枚が行きのぶんで、残りが帰りのぶんです。

東京都区内→名古屋市
片道6260円(学割5000円)
名古屋市内→大阪市
片道3350円(学割2680円)
大阪市内→東京都区内
片道8750円(学割7000円)

合計すると、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以下の乗車券は、途中下車できません。
  • 東京・大阪・福岡・仙台・新潟の各大都市近郊区間内で完結する乗車券は、途中下車できません。
    • 東京近郊区間大阪近郊区間は、それぞれ首都圏・近畿圏のおおむね全域と考えていいと思います。
    • 福岡・仙台・新潟の各近郊区間は、私が地理に疎いせいであまりうまくパッと説明できないので、Wikipedia等を参照してください。
    • 東京・大阪の各近郊区間も、詳しい範囲はWikipedia等を参照してください。
    • 2014年4月1日に東京近郊区間が拡大されたことに伴って、いわきから松本まで450km近くあるのに途中下車できなくなるということは鉄道クラスタの間では話題になりました。
  • 乗車券の発駅もしくは着駅が、東京都区内や東京山手線内、○○市内となっているときは、その都区内・市内の駅
    • 発駅・着駅を東京都区内や東京山手線内、○○市内と表記するのは、特定都区市内制度と言います。
    • 各都区・市内の駅と、その中心駅から201km以上ある駅との間の運賃は、その中心駅からのキロ数で計算します。
      • 東京山手線内の駅であれば、東京駅から101km以上200km以下の駅との間であっても、東京駅からのキロ数で計算します。
    • 東京都区内は東京23区内、東京山手線内は山手線と中央快速線(神田~新宿)、中央・総武緩行線秋葉原~代々木)の駅が該当します。
    • ○○市内に該当する市は、横浜・名古屋・京都・大阪・神戸・広島・北九州・福岡・仙台・札幌です。
    • ○○市内になる駅は、ほぼその市の範囲に一致しますが、先ほど出てきた川崎駅の例のように一部例外があるので、Wikipedia等で確認することをおすすめします。
    • 特定都区市内発着の乗車券には、「券面表示の都区内(市内)下車前途無効」などと書いてあると思います。
    • 青森から上京した人が~~~の例で上野駅などで途中下車できないのは、これが理由です。

なお、新幹線は基本的に大都市近郊区間には含まれない(例外として、東海道新幹線の米原~新大阪と山陽新幹線西明石~相生は大阪近郊区間に含まれます)ので、例えば東京から熱海に行くのに小田原で一旦下車したい、というような場合には、在来線経由ではなく新幹線経由を指定するといいです。
新幹線経由を指定していても、新幹線駅に限らず、平行する在来線の各駅で途中下車することができます。*2

乗車券の有効期間

冒頭にも書きましたが、乗車券の経路の途中で一旦下車したあと、再び改札の中に入って移動を続行するには、その乗車券の有効期間内であることが必要です。

片道乗車券の有効期間は100kmまで1日、200kmまで2日、それ以後は200kmごとに1日追加されます(したがって例えば東京から大阪までの片道乗車券は、556.4kmなので、4日間有効です)。

往復乗車券の有効期間は、片道乗車券の2倍です。

また、移動の途中で乗車券の有効期間が切れても、途中下車をしなければ着駅まで行くことができます。

できれば乗車券をひとつづきにさせたいが、自分の予定と有効期間の関係でできないというような場合に、有効期間を無理矢理伸ばす方法も存在しますが、説明が入り組みますので今回は省きます。

最後に

JRの旅客営業規則はとても複雑です。
しかし、その中にはうまく使うと、トクトクきっぷの類を使わなくてもとてもお得になるような規則も存在します。
また、無理矢理一筆書きの乗車券にしてしまうだとかの工夫の余地もたくさん存在します。
ぜひこの乗車券パズルを楽しんでみてください。

*1:回数券や定期券以外の乗車券のことで、片道乗車券、往復乗車券、連続乗車券の3種があります。連続乗車券はあまり一般的ではないので、この記事では触れないことにします。

*2:じゃあできるだけ新幹線経由にしたほうがいいじゃんと思うかもしれませんが、大都市近郊区間内相互発着だと乗車券の経由によらず一番便利な経路で乗車することができるというメリットがあります。

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できる最大数を制限するのはインクルードファイルの階層数よりももはや数値定数の最大値だとかコンパイル時間だとかそっちになってくると思います。

もっとコードを短くスマートにできるのかもしれませんが、私にはそのスキルはありませんでした。

コンパイル時実行バンザイ!

04/29 20:54追記

gccだと私の環境ではFIZZBUZZ_MAX=27622くらいでgccのメモリ不足(?)でコンパイルできなくなりました。
MSVCはFIZZBUZZ_MAX=99999でも(コンパイルに時間はかかりますが)コンパイルできます。

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<>に変換して汚物を消毒。

長々と読んでいただきありがとうございました。

*1:文中で上限下限いってるのがわかりにくそうですが、そういえばC#ではArray.CreateInstanceメソッドを使えば0-indexedではない配列も作れたりするのでした。つまり、「その配列のインデックスとして指定できる値の上限下限」ということですね

*2:x86アセンブラとかだと要素データの大きさによっては1命令でできたりもします

*3:多次元配列はジャグ配列と比べて四角形状配列になることが文法的に保証されるという安心感がなくなるデメリットはありますね

BeagleBone Blackで7セグLEDをダイナミック点灯する方法

みなさんあけましておめでとうございます。今年もよろしくお願いします。

さて今回のネタは何かというと、プログラミングっぽさが少し減りますが、BeagleBone Blackで7セグLEDをダイナミック点灯する方法を扱いたいと思います。

BeagleBone Blackとは?

簡単にいうと手のひらサイズのLinuxボードです。最近だとRaspberry Piが人気(かつ有名)だと思いますが、Raspberry PiよりもIOが充実していてしかも高性能です。ただし、日本語の資料がRaspberry Piよりも圧倒的に少ないのが難点なので、入門には少しハードルが高いと思います。ただし、最近は関連書籍も少しずつ出版されてきていたりして、日本語資料は今後どんどん増えていくと思います。

最新のリビジョンは2013/01/26現在、Rev. A6Aです。リビジョンによって中身にそこまで差があるわけではないですが、リビジョンが上がるにつれてバグフィックスがされているようなので、できるだけ最新のリビジョンのものを購入するようにしましょう。

また、名前の似た製品としてBeagleBoneがあります(Blackがない)が、これはBeagleBone Blackの前の製品で、BeagleBone Blackよりもスペックが低い上に高いので、買わないように注意しましょう。

最近では秋月でも取り扱いをしているようですが、すぐ売り切れてしまっているようです。

回路設計

BeagleBone Blackは各IOピンの入出力電流の制限が結構シビアです。共通のVDD_5V端子については1000mA、VDD_3V3とSYS_5V端子については250mA流せます*1が、その他GPIOについては3mAしか流せません。

また、今回の記事を書くネタになったものは7セグLED以外にもたくさん入出力部があってそのままではBeagleBone BlackのGPIO端子だけでは足りなくなってしまうので、TC4511BP*2という7セグLEDのドライバICを使って信号線の本数を減らすことにします。

このドライバICは基本的には4本の入力をとって、それを4桁の2進数と解釈してその数に対応する7セグの発光パターンを出力するというものです*3。(アノードコモンではなく)カソードコモンのLEDを直接駆動できるので便利です。

ということで回路図はこんな感じになります。
f:id:yasuand:20140126074830p:plain

本当はこの回路図はあまりよくない(1つの抵抗に2つ以上のLEDがつながっている)のですが、今回はダイナミック点灯ということで2つ以上7セグが同時に点灯するのはありえない(その辺の制御はソフト側で行う)ので、問題ありません。

点灯試験や全消灯に使う端子はVCCに釣り上げておいて、また今回は.の表示はしないのでそこはGNDに落としておきます。.の表示をしたい場合はGNDに落とさずにトランジスタを介して制御しましょう。トランジスタを何個もブレッドボード(もしくは基板)上に並べるのはコンパクトでないので、TD62003APGのようなトランジスタアレイを使えばいいと思います。

また、4511のLE端子が今までの説明にないのにもかかわらずBeagleBone Blackから入力を取るように書かれていますが、これの役割は後述します。プログラムを書くときはとりあえずそこには0を出力しておきましょう。

とりあえず1桁だけ点灯させてみる

回路が組み終わったら点灯試験です。

4桁をいきなりダイナミック点灯させるのは難しいので、1桁だけ点灯させてみたいと思います。

GPIOに出力するには、まず"/sys/class/gpio/export"という特殊ファイルに出力に使いたいGPIOの番号を書き込み、次に"/sys/class/gpio/gpio(GPIO番号)/direction"に"out"を書き込むとそのGPIO端子が出力として使えるようになって、0を出力するには"/sys/class/gpio/gpio(GPIO番号)/value"に0を、1を出力するには1を書き込み、値の書き込みが終わってもうそのGPIOを使わないとなったら"/sys/class/gpio/unexport"にGPIO番号を書き込めばいいのでした。

例えばP9ヘッダ(Ethernetアダプタが上にくるように見たときに左側にくる方のヘッダ)の11番ピンを使う場合は、そのGPIO番号は30なので、

#!/bin/sh

echo 30 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio30/direction
echo 0 > /sys/class/gpio/gpio30/value
sleep 1
echo 1 > /sys/class/gpio/gpio30/value
sleep 1
echo 0 > /sys/class/gpio/gpio30/value
echo 30 > /sys/class/gpio/unexport

などとシェルスクリプトを書いてGPIO端子にLEDをつなげば、1秒後にLEDが光ってさらにその1秒後にLEDが消灯するようになるわけですね。

UbuntuのBeagleBone Black用イメージの場合、gccが最初からは入っていない一方、Pythonは最初から入っているのでPythonでプログラムを書いてみます*4。ピン番号は適当に変更してください。このプログラムのままピン番号を変更しない場合、4511の1, 2, 5, 6, 7番ピンがそれぞれBeagleBone BlackのP9ヘッダの11, 13, 15, 21, 23番ピンに、4桁の7セグのうち1番上の桁に相当するトランジスタアレイのピンをBeagleBone BlackのP9ヘッダの12番ピンにつなげてください。他の桁については接続せずに放置しておきましょう。

# -*- coding: utf-8 -*-

import os
import time

class GpioWriter:
    def __init__(self, pinNumber):
        self.pinNumber = pinNumber
        os.system("echo " + str(pinNumber) + " > /sys/class/gpio/export")
        os.system("echo out > /sys/class/gpio/gpio" + str(pinNumber) + "/direction")
    
    def release(self):
        os.system("echo " + str(pinNumber) + " > /sys/class/gpio/unexport")
    
    def setBit(self, bit):
        os.system("echo " + str(bit) + " > /sys/class/gpio/gpio" + str(self.pinNumber) + "/value");

if __name__ == "__main__":
    digits = map(lambda x: GpioWriter(x), [ 49, 30, 31, 3 ]) # 左から順にP9ヘッダの23, 11, 13, 21番ピン
    le     = GpioWriter(48)                                  # P9ヘッダの15番ピン
    enable = GpioWriter(60)                                  # P9ヘッダの12番ピン
    le.setBit(0)
    enable.setBit(1)
    for i in xrange(10):
        for j in xrange(10):
            for k in xrange(4):
                digits[k].setBit((j >> k) & 1)
            time.sleep(0.5)
    enable.release()
    le.release()
    for d in digits:
        d.release()

これは0.5秒毎に7セグに表示される数字がインクリメントされるのが10周期続いて終わるというプログラムですが、これを実行してみるとちょっと表示が"変"になるはずです。

具体的にどこが変かというと、3->4とか7->8とかに遷移するときにわかりやすいと思いますが、表示したくないセグメントまで一瞬表示されるのが目で確認できるレベルで起こってしまうということです(実際に組んでみてやってみるのが一番わかりやすいと思います)。

これの原因は何かというと、GPIOへの書き込み速度です。0~9の数字を2進数で表したときの各桁の更新は同時にGPIOに反映されるわけではなく、しかもそのタイムラグは無視できるものではないので、数字を更新しているときに変な表示になってしまうわけですね。

これを解決するために出てくるのが4511のLE端子です。

4511はLE端子に0が入力されている間は入力されている4桁の2進数に応じた7セグの発光パターンを出力しますが、1が入力されていると、前に出力していた発光パターンを維持します。つまり、A~Dの入力に何が来ようが出力はそのままということです。

ということでプログラムを書き直します。変更箇所はかなり少ないです。

# -*- coding: utf-8 -*-

import os
import time

class GpioWriter:
    def __init__(self, pinNumber):
        self.pinNumber = pinNumber
        os.system("echo " + str(pinNumber) + " > /sys/class/gpio/export")
        os.system("echo out > /sys/class/gpio/gpio" + str(pinNumber) + "/direction")
    
    def release(self):
        os.system("echo " + str(pinNumber) + " > /sys/class/gpio/unexport")
    
    def setBit(self, bit):
        os.system("echo " + str(bit) + " > /sys/class/gpio/gpio" + str(self.pinNumber) + "/value");

if __name__ == "__main__":
    digits = map(lambda x: GpioWriter(x), [ 49, 30, 31, 3 ]) # 左から順にP9ヘッダの23, 11, 13, 21番ピン
    le     = GpioWriter(48)                                  # P9ヘッダの15番ピン
    enable = GpioWriter(60)                                  # P9ヘッダの12番ピン
    enable.setBit(1)
    for i in xrange(10):
        for j in xrange(10):
            le.setBit(1)          # 発光パターンの変更をロック
            for k in xrange(4):
                digits[k].setBit((j >> k) & 1)
            le.setBit(0)          # 発光パターンの変更を反映
            time.sleep(0.5)
    enable.release()
    le.release()
    for d in digits:
        d.release()

これでスムーズに0~9の数字が移り変わるようになったはずです(パチパチ

いよいよダイナミック点灯

数字を1桁表示することができたので、いよいよダイナミック点灯させてみます。

ためしに"1234"という数字のパターンを出力してみましょう。先ほど放置していたピンは、上位から順にP9ヘッダの16, 22, 24番ピンにつなげてください。

# -*- coding: utf-8 -*-

import os
import time

class GpioWriter:
    def __init__(self, pinNumber):
        self.pinNumber = pinNumber
        os.system("echo " + str(pinNumber) + " > /sys/class/gpio/export")
        os.system("echo out > /sys/class/gpio/gpio" + str(pinNumber) + "/direction")
    
    def release(self):
        os.system("echo " + str(pinNumber) + " > /sys/class/gpio/unexport")
    
    def setBit(self, bit):
        os.system("echo " + str(bit) + " > /sys/class/gpio/gpio" + str(self.pinNumber) + "/value")

if __name__ == "__main__":
    digits  = map(lambda x: GpioWriter(x), [ 49, 30, 31,  3 ]) # 左から順にP9ヘッダの23, 11, 13, 21番ピン
    le      = GpioWriter(48)                                   # P9ヘッダの15番ピン
    enables = map(lambda x: GpioWriter(x), [ 60, 51,  2, 15 ]) # 左から順にP9ヘッダの12, 16, 22, 24番ピン
    number  = [ 1, 2, 3, 4 ]                                   # 表示する数字列
    count = 3
    for i in xrange(3000):
        for j in xrange(4):
            n = number[j]
            le.setBit(1)                       # 表示する数字の変更をロック
            for k in xrange(4):
                digits[k].setBit((n >> k) & 1)
            enables[count].setBit(0)           # 今点灯している桁を消灯する
            le.setBit(0)                       # 表示する数字の変更を反映する
            count = (count + 1) % 4            # 点灯する桁を移動
            enables[count].setBit(1)           # 次の桁を点灯する
            time.sleep(0.001)
    for e in enables:
        e.release()
    le.release()
    for d in digits:
        d.release()

さて、これを実行するとうまく点灯するでしょうか。

実際には確かに表示される桁が移り変わって見えますが、周期が遅すぎィ!という感じになると思います。

キレイにダイナミック点灯するために

なぜ周期が遅くなって、ダイナミック点灯がキレイにできないかというと、これもやっぱりGPIOの書き込み速度のせいです。

そういえば1桁だけをスタティック点灯させたときは、4511のLE入力を使って解決して、根本的な解決はしていないのでした。

BeagleBone Blackには24.576MHzで書き込みができる端子が(確か)2つ用意されていますが数が少なすぎで7セグのダイナミック点灯には厳しそうだし、そもそも"/sys/class/gpio/gpio30/value"を通じた書き込みが常識的に考えられる以上に遅すぎます*5

ということでこの解決方法はかなり難しそうなのですが、実際にはかなり簡単です。echoを使って書き込まずに、PythonのファイルIOを使って直接書き込んでしまえばいいのです*6

# -*- coding: utf-8 -*-

import os
import time

class GpioWriter:
    def __init__(self, pinNumber):
        self.pinNumber = pinNumber
        os.system("echo " + str(pinNumber) + " > /sys/class/gpio/export")
        os.system("echo out > /sys/class/gpio/gpio" + str(pinNumber) + "/direction")
        self.fd = os.open("/sys/class/gpio/gpio" + str(pinNumber) + "/value", os.O_WRONLY)
    
    def release(self):
        os.close(self.fd)
        os.system("echo " + str(self.pinNumber) + " > /sys/class/gpio/unexport")
    
    def setBit(self, bit):
        os.write(self.fd, str(bit))

if __name__ == "__main__":
    digits  = map(lambda x: GpioWriter(x), [ 49, 30, 31,  3 ]) # 左から順にP9ヘッダの23, 11, 13, 21番ピン
    le      = GpioWriter(48)                                   # P9ヘッダの15番ピン
    enables = map(lambda x: GpioWriter(x), [ 60, 51,  2, 15 ]) # 左から順にP9ヘッダの12, 16, 22, 24番ピン
    number  = [ 1, 2, 3, 4 ]                                   # 表示する数字列
    count = 3
    for i in xrange(3000):
        for j in xrange(4):
            n = number[j]
            le.setBit(1)                       # 表示する数字の変更をロック
            for k in xrange(4):
                digits[k].setBit((n >> k) & 1)
            enables[count].setBit(0)           # 今点灯している桁を消灯する
            le.setBit(0)                       # 表示する数字の変更を反映する
            count = (count + 1) % 4            # 点灯する桁を移動
            enables[count].setBit(1)           # 次の桁を点灯する
            time.sleep(0.001)
    for e in enables:
        e.release()
    le.release()
    for d in digits:
        d.release()

これでキレイにダイナミック点灯できましたね(パチパチパチパチ

おまけ

これで7セグのダイナミック点灯に耐えられるくらいの書き込み速度でGPIOに出力することができるようになったわけですが、実際に作るような回路はもっと複雑で、出力がもっとたくさんあったり、入力もあったりするはずです。

入出力をとるときは本当はこのようにメインプログラムの中にベタ書きするのではなくマルチスレッド化したりすると思いますが、このときGPIOへの入出力をする部分が1つでも

os.system("echo 1 > /sys/class/gpio/gpio30/value")

というように書いてしまっていると、この処理に時間がかかって結果的にときどきチラつくというような動作になってしまいます。

今回の記事では出力だけを扱って入力は扱いませんでしたが、入力についても

class GpioReader:
    def __init__(self, pinNumber):
        self.pinNumber = pinNumber
        os.system("echo " + str(pinNumber) + " > /sys/class/gpio/export")
        os.system("echo in > /sys/class/gpio/gpio" + str(pinNumber) + "/direction")
        self.fd = os.open("/sys/class/gpio/gpio" + str(pinNumber) + "/value", os.O_RDONLY)
    
    def release(self):
        os.close(self.fd)
        os.system("echo " + str(self.pinNumber) + " > /sys/class/gpio/unexport")
    
    def getBit(self):
        os.lseek(self.fd, 0, os.SEEK_SET)
        bytes = os.read(self.fd, 1)
        if bytes.isdigit():
            result = int(bytes)
            return result
        else:
            return None

などとして取得するようにすると、GPIOの性能を十分活かした入出力ができます*7

最後に

BeagleBone Blackは安くて高性能な素晴らしいLinuxボードですが、日本語資料が少ない上、過電流保護回路もない(実際僕は1個ふっ飛ばしました)ので、取り扱いには十分注意が必要です。しかし、正しく使えばとても良いものだと思うので、日本にもっともっと普及して日本語資料も充実してみなさんが素晴らしいBeagleBone Blackライフを贈れることを願っています!

*1:VDD_5V端子はDC電源からとってきたときに供給されて、SYS_5V端子はUSBからの給電のときに供給されるらしい…?

*2:これは東芝が出しているICですが、別に日立が出している74HC4511とかでも特に変わらない思います。いろいろ調べていて「具体的な品番を出してくれよ!!」と結構思ったので具体的な品番を出しました。とりあえず4511シリーズであるというのが重要です。

*3:個人的には6と9の字形が気に入らないのですがしょうがない

*4:python触ったのは実はこれが初めてなので、Pythonのプログラムとしては少し変になってるかもしれません…

*5:せいぜい10Hzくらいしか出てないのでは…?

*6:もちろんPythonに限らずC++とかJavaとかでも同じはずです

*7:このようにファイルディスクリプタ経由で値を読み込むと、実際には1byteも読み込めていないという瞬間が結構出てくるので、本当に値が読めているかどうかのチェックが必要です

素因数分解botについて

おはようございますこんにちはこんばんは。

一部の界隈にはどうも今日は大事な日らしいですが、いかがお過ごしでしょうか。


ということで6時間くらい内容考えて、そういえば5月くらいに稼働を始めてから一度も素因数分解bot(@yasuand_bot_f)について全く詳しい説明とか書いてないというのを思い出したので書きます。

素数だ!うおおおおおおおおおおおおおおおおおお!!!*1

何をするbot

まず。ツイ廃あらーととツイート数カウントくんで自動的に投稿される、



というようなツイートに含まれる、その日のpost数を素因数分解します。

ツイ廃あらーとで流れてくる、


というようなツイートは素因数分解の対象ではありません。

また、



というように直接@で数字を投げても素因数分解します。

他の機能

post数が素数だったり、@で素数を投げられるとふぁぼります。

また、その日の00:00:00~00:10:00の間にツイ廃あらーとで流れてきたpost数のうち、最小素因数が一番大きい人に対して表彰をして、対象のツイットをふぁぼります。ツイート数カウントくんの自動投稿は表彰機能の対象外ですし、またツイ廃あらーとの投稿であっても残念ながら00:00:00~00:10:00の間には流れてこなかったものも表彰機能の対象外です*2

最小素因数は、「合成数nの素因数のうち一番小さいもの」という意味で使っています。つまり、post数が素数(もしくは1)であれば自動的に表彰機能の対象外になります。

どうやって動かしているか

アルゴリズムは簡単で、UserStreamで流れてきた投稿に対して正規表現にマッチングするかどうか調べて、それと同時に投稿文字列からpost数を表す文字列を抜き出してそれを数値に変換して素因数分解しているだけです。

素因数分解のアルゴリズムも単純で、単に試行除算しているだけです。難しいなんちゃら法とかよくわかんないし。

制限

post数が1だったりだとか、@で1を投げられると素因数分解しません。最初はそのまま1と返していましたが、あまりにも味気ないしいい返しも思い浮かばなかったのでスルーすることにしました。

数字の上限についても、大きい数字を投げられてもわりと平気で、Rubyで書いているので自動的に多倍長整数の演算をしてくれます。Ruby賢い。

ただし、やはり140桁の整数*3が素数かどうか判定しろとかいわれても現実問題としてこれはほぼ不可能に近いので、試行除算の回数が一定数以上だったり素因数分解後の結果が140字におさまらないときはスルーします。

素因数分解しないときにスルーする仕様はユーザに対して優しいのかどうかちょっと悩みどころ。

ツイ廃あらーととツイート数カウントくんについては投稿元クライアントの名前とURLも見ているので、他クライアントから文字列をコピペしたり改変したりして呟いても素因数分解しません。@で直接数字を投げた場合は投稿元クライアントは見ていないので素因数分解します。

また、全角数字は数字ではないので、全角数字が混ざっていると素因数分解しません。

なぜ作ったか

そのときのノリで作った。後悔はしていない。

botを作って得られたもの

  • 正規表現バリバリに使うので正規表現の使い方。
  • Rubyスクリプトをサーバ上でデーモンとして動かす方法。
  • Twitterの諸々の仕様についての理解。

最後に

こういうクソbotでもいいので、知っている/使える技術を組み合わせて何か作るのは学習のモチベにもなるのでとてもいいと思います。何か作るときはたいてい自分が今使える技術だけでは作れなくて何か調べたり考えたりしながら作らないといけないと思っているので、モチベの向上以外にも実際に技術を修得するのにとても役立ちそうです。

実際に手を動かす作業はプログラミングとかに限らずどの分野でもとても大事な作業だと思うので、(僕私にはあまりいいもの面白いものが作れないから…)などと尻込みしている人は是非挑戦してみましょう。

それではよいお年を

*1:当方、東工大生ではなく東大生です

*2:最近ツイ廃あらーとでその日のpost数が流れてくるのがやや遅れ気味なので、もしかするとそのうち00:15:00までにしたりするかもしれませんが、今のところ変えるつもりはありません

*3:実際には@yasuand_bot_f の部分を抜くので最大125桁

Box2DのポリゴンをC++から使うときの注意点

こんばんはおはようございますこんにちは。

今回は、実験の自由課題でBox2Dを使って物理シミュレーションをしたのですが、そのときにちょっとハマった点を書きます。

Box2Dとは

ハマったところを書く前に、とりあえずBox2Dの簡単な紹介です。

Box2D(http://box2d.org/)は、2次元平面上での物理シミュレーションをするためのライブラリです。zlibライセンスで配布されています。

2013/12/02現在の最新版はBox2D 2.3.0です。

もともとはC++用のライブラリですが、JavaとかActionScriptとか.NETとか向けに有志によって移植されたものもあるようです。

ポリゴンの座標系

さてここがハマったところです。

ちなみに、コーディング中はずっと、Box2Dを公式サイトから落としてくるときについてくるドキュメントはもちろんのことながら、それに加えてnitoyon氏のてっく煮ブログの記事を参考にしてました(情報が古いししかもC++じゃなくてActionScriptですが)。

ポリゴンを生成するとき、次のようなコードを書いたとします。

b2Vec2 grabity(0.0f, 9.8f);   // 重力加速度9.8
b2World world(grabity);
b2BodyDef polygonDef;
polygonDef.type = b2_dynamicBody;
polygonDef->positon.Set(0.0f, 0.0f); // 位置を原点にセット
auto polygonBody = world.CreateBody(&polygonDef);
const int verticesCount = 5;   // 頂点数
b2Vec2 vertices[verticesCount];
vertices[0].Set(1.0f, 1.0f);
vertices[1].Set(2.0f, 1.0f);
vertices[2].Set(3.0f, 2.0f);
vertices[3].Set(2.0f, 3.0f);
vertices[4].Set(1.0f, 2.0f);
b2PolygonShape polygonShape;
polygonShape.Set(vertices, verticesCount);
b2MassData polygonMassData;
polygonShape.ComputeMass(&polygonMassData, 1.0f);  // 密度は1.0
b2FixtureDef polygonFixture;
polygonFixture.friction = 0.3f;    // 摩擦係数0.3
polygonFixture.restitution = 0.7;  // 反発係数0.7
polygonFixture.density = 1.0f;     // 密度1.0 上で設定してるから多分書かなくていい
polygonFixture.shape = &polygonShape;
polygonBody->CreateFixture(&polygonFixture);
polygonBody->SetMassData(&polygonMassData);

これでworldにポリゴンを設定できたと思います(実際に実験課題で書いたソースコードは該当部分が都合上断片化してたりしてもしかすると少し足りなかったりコンパイル通らなかったりするかもしれませんが、ご容赦ください)。

さて、参考にしたと上で書いたブログ(上のリンクとは同じブログ内の別のページだったかもしれませんが)には、「polygonBody->GetPositon()して得られるのは重心の座標」みたいなこと書いてあったように思います(書いてなかったらすみません)。

しかし実際に動かしてみると、次のようになりました。

  • polygonBody->SetPosition()したときに設定する座標は、そのポリゴンの位置を参照するときに使う基準の座標
  • 当たり判定は普通に、polygonShape.Setで設定した座標を基準として行う
  • polygonBody->GetPosition()で返ってくる座標は、polygonShape.Setで設定したポリゴンの重心の座標が平行移動したぶんだけ、polygonBody->SetPosition()で設定した座標を平行移動させたもの

要するに、polygonBody->SetPosition()だとかpolygonBody->GetPosition()だとかは重心云々とはそんなに関係ないようです。

実験課題ではこれまわりのデバッグに一番時間をとられました。似たものがたくさんでてきて非常にややこしい。

ポリゴンの頂点数

Box2Dの公式ドキュメントには「ポリゴンの頂点数はb2_maxPolygonVertices(デフォルトでは8)以下じゃないとダメ」とか書いてありますが、高精度なポリゴン計算をしようとしてこの値を動的に書き換えようとしても、b2_maxPolygonVerticesはなんと次のように定義されてるので動的には書き換えられません。

#define b2_maxPolygonVertices 8

また、余裕をもって大きな値にしようとしてもメモリ制限か何かでそれはできないようです。

1つの物体に対して複数の図形を登録して、登録した図形があわさった形を作ることはできるようなので、頂点数が多い図形やそもそも凸でない図形を扱いたいときはおとなしくポリゴンの三角形分割とかを考えましょう(上のてっく煮ブログの記事に、八角形に分割するActionScriptのコードが載っているので、それを真似してC++で書けばできるんじゃないかなあと思います)。

その他

この記事を書いてて気づいたんですが、Box2D 2.3.0から、ポリゴンの頂点を指定するときに反時計回りの凸包じゃないといけない云々は考えなくてもよくなったようですね。頂点を指定するときに自動的に凸包を計算して反時計回りに頂点が並ぶよう修正してくれるようです。それ以前のバージョンでは凸包を計算する(座標直打ちのときはその頂点が本当に凸包になっているかどうか計算する)必要があって、また時計回りには頂点が並ばないよう注意する必要があったので、かなり楽になったようですね。

また、ある程度以上近いポリゴンの頂点は併合されてしまうようになったらしいので、あまりに小さいポリゴンを作りたいときなどには注意が必要です。

最後に

実験課題ではそんなに重くなるような計算をさせたわけではないので、計算が高速かどうかはよくわかりませんが、2Dシミュレーション向けには結構使えそうですね。

Web上に日本語資料があまりないのが難点ですが。