シャンテン数アルゴリズムの高速化について
「シャンテン数や点数計算をする時間」
ここはどうしても高速化することになるし、重要なところ(個人的には、まだ時期尚早というか、他にやるべきことがあると考えているが)。
最強のシャンテン数計算アルゴリズムなんてよくわからないので、今回考えるのはhttp://mahjong.ara3.net/etc/shanten/index.htm
の高速化。
インデックスを持っておいて、その組み合わせで求めるアルゴリズム、すごく高速っぽいし自分も使わせてもらってるんだけど、一人麻雀練習機に付属してるハッシュ表は、ハッシュ関数がゴミ。
擬似コードを示すと、このようなものを使っている。
int hash(int t[9]) { // t[i]がkなら、i萬がk枚という意味 int ans = 0; for(int i = 0; i < 9; i++) ans = ans * 10 + t[i]; return ans; }
これは10^9くらいの大きさになるので、配列にとっておけなくて(連想配列を配列で実装できない)、平衡二分木を使う必要がある。平衡二分木の検索にはO(logN)かかる。普通はlogなんてそんなに気にすることでもないけど、消せるもんは消そう。*1
また、連想配列のサイズを減らすため、孤立牌を除くのも必要になっていて、コードも長くなるし、遅い。
ハッシュ関数を次のようなものに変更してみる。
int hash(int t[9]) { int ans = 0; for(int i = 0; i < 9; i++) ans = ans * 5 + t[i]; return ans; } }
これなら、ハッシュ値は最大でも5^9=1953125くらいで、余裕で配列に取れる。つまり、O(logN)だった検索部分がO(1)になる。また、孤立牌を消す必要もない。
これは確実にかなり効くと思う。
さらに高速化したいなら、そもそも手牌を(int, int, int)という、そもそもハッシュ化された状態で持つのがいいと思う。
ところでちょっと気に入らないのが、ans*"5"っていうところ。5だと、ビットマスクで取り出せないのでパフォーマンス的に悪そう。(modの遅さは異常)
同じ牌を4枚持ってるってあんまりないので、そこは別の形で持って、ans*"4"にするっていうのも思いついたけど、上手く速くなるか微妙。ってかあんまり速くなる気がしない。
ここから余談
interface Tehai { void add(Pai pai); void remove(Pai pai); int getShanten(); bool isTenpai(); List<Naki> listNaki(Pai pai); }
こんな感じのinterfaceを用意して、それを使ってコーディングしてれば、内部実装をあとで変えるのは簡単にできる。
まずは、
class ArrayTehai implements Tehai { private List<Pai> tehai; public ArrayTehai() { this.tehai = new ArrayList<Pai>(); } public void add(Pai pai) { tehai.add(pai); } (以下略) }
みたいなナイーブな実装でごまかしておく。
*1:ハッシュマップでもいいけど、汎用的なものを使うとどうしてもオーバーヘッドがかかる