SpreadSheetでトランザクションを管理してポーカー大会をした
この記事はSpreadsheets/Excel Advent Calendar 2018の最終日の記事です。
tl;dr
- Google Spreadsheetにポーカーチップのやり取りを書く。
- Google Apps Scriptでポーカーチップの変化とランキングをかっこよく出す。
ポーカー
ポーカーをご存知ですか?トランプゲームのやつです。日本で広く知られているのは5枚配られ、1回交換して、強い役を作って楽しむゲームです。または、1枚配ってのインディアン・ポーカーのほうが有名かもしれません。
海外において主流なのはテキサスホールデムポーカーです。 世界で最も賞金の高い大会としても知られています。 積み上がっているお札がやばい。 www.youtube.com
ポーカー大会
というわけでサークルでポーカー大会をすることにしました。 1日でやっても良かったのですが、2時間程度のテーブルを14日開催する形式にしました。
チップのやり取りの管理のために、Spreadsheetの力を借りました。 別の手の抜き方として、Firebaseを使うことも考えましたが、 やり取りの入力をするのが自分だけであることからOverkillな感じがしました。参加者も20人程度だったので大丈夫でしょう。
見た目
セキリュティーやプライバシーのためにサイトそのものの公開はしませんが、見た目はこんな感じです。
Spreadsheetそのものよりは遥かにかっこいいですね。 僕がSpreadsheetをいじることでこのサイトが自動で更新されます。
その下には個別のゲームの結果がずらずらと並びます。
トランザクションを手で書く
新しいシートを作り、トランザクションを手で書きます。
全体シートに1行追加して、シート名を書きます。
関数で頑張っているので、更新はこれで終わりです。 DBとしては正規化が効いていなくて最悪な感じもしますが、使い捨て&楽なので良しとします。
Apps script側
あまり真面目に解説はしません。デプロイなどの仕方は他の記事を見てください。割と力技です。自分だけが入力する以上、汚染されたデータが一切存在しないと考え、セキリュティーについては考えていません。
注意点としては、doGetで処理をするとレスポンスに時間がかかります。 そのため、データを取得するところは別の関数(getData)にして非同期に呼び出しましょう。
//コード.gs抜粋 function doGet() { return HtmlService .createTemplateFromFile('index') .evaluate(); } function getData() { const db = getDB(); const tbl = getTable(db, "Stack"); const res = {}; res.stack = []; for (var i = 1; i < tbl.length; i++){ res.stack.push({user:tbl[i][0], stack:tbl[i][1]}); } const tnms = tbl[0].slice(2, -1); res.tnms = [] for (var i = 0; i < tnms.length; i++){ var tnm = getTable(db, tnms[i]); res.tnms.push({name: tnms[i], desc: tnm[0][0], result: tnm.slice(2)}); } Logger.log(res); return res; }
//index.html抜粋 <script> // The code in this function runs when the page is loaded. $(function() { google.script.run.withSuccessHandler(show).getData(); }); function show(data) { const list = $('#ranking'); const stack = data.stack.sort((a, b)=>b.stack - a.stack); for (var i = 0; i < stack.length; i++) { const c = Math.max(0, Math.floor((stack[i].stack - 1000) / 100)); list.append(`<tr><td>${i + 1}`+ `<td>${stack[i].user}`+ `<td>${stack[i].stack}`+ `<td>${c}:${(new Array(1 + c)).join("🍫")}`); } const tlist = $("#tlist"); for (let tnm of data.tnms) { let table = `<h3>${tnm.desc} (${tnm.name})</h3><table><tr><th>Rank<th>Name<th>Pay<th>Get`; const result = tnm.result;//.sort((a,b)=>b[2]-a[2]); for (var i = 0; i < result.length; i++) { table += `<tr><td>${i + 1}`+ `<td>${result[i][0]}`+ `<td>${result[i][1]}`+ `<td>${result[i][2]}`; } table += `</table>` tlist.append(table); } } </script>
この部分と、getDataの実装をすれば終わりでした。 とても手軽ですね。 最初に出てきたわけのわからないグラフも、Spreadsheetで作ったグラフを埋め込むだけです。楽ちん。
運営してみて
一番めんどくさいところはやはりトランザクションの入力です。 でもこれはそもそもどうしようもない手間です。 これ以上楽するためにはポーカーチップにチップ埋め込むしかないでしょう。
トランザクションの出入りが釣り合わないときに上にSum関数を書くだけでValidation出来たのはSpreadsheet様様という感じでした。
Slack
Apps Scriptなので、毎日スクリプトを走らせることが出来ます。 ついでにサークルのSlackに今日現在のランキングを流していました。
ところで、
ランキングに書かれていた🍫は、インセンティブでした。最終日がバレンタインデーだったわけです。
「max(0, floor((stack - 1000) / 100))
チョコをwass80からプレゼント🍫」
という文言を終わり際になって用意しておきました。(初期stack = 1000)
最適戦略は全チップを他人に譲り渡してその人とチョコを分け合うことになります。もし起こったら悲しかったですが、そうはならなかったです。
これのために初めて人にチョコを包む経験をしました。 中身はフルタの安いけど美味しいチョコレートです。 人によっては外側の袋のほうが高い感じになります。 それでも、袋に包むとめっちゃ良いものに見えるのでおすすめです。
僕は19位/20人という結果に終わりました。精進が必要です。
次回予告
最近はコントラクトブリッジにハマっているので、次はデュプリケートブリッジの大会をしたいですね。
- 出版社/メーカー: ジーピー
- メディア: おもちゃ&ホビー
- 購入: 1人 クリック: 8回
- この商品を含むブログ (6件) を見る
京都小旅行 大徳寺→たこ焼き→船岡温泉→ステーキ
突発的な観光のお時間です。
今回は船岡温泉周辺のスポットに行きました。
まず大徳寺龍源院を眺めました。 このサイズ感、ボドゲの枯山水にそっくりですね。(拝観料350円)
大徳寺はめちゃくちゃ敷地が広く、拝観できる場所も4つあるらしいです。それぞれ拝観料が取られるので諦めて龍源院だけ見ました。龍源院には、日本に伝来した最古の火縄銃や、蒔絵の碁盤などが飾られていました。歴史的ですね。
西に出て堀川通にあるたこ焼き屋「すずや」(8個500円) 関西にはあまりない大ぶりのタコが入っていて満足度が高い。ソースが旨味が強くて美味しい。
雨が降ってきたので更に北上するのをやめて船岡温泉に向かいます。 船岡温泉の最高さは他の記事を見てください。京都で最も格式の高い銭湯です。彫り物とタイルがものすごく立派です。写真を取りたくなるんですが、脱衣所内は撮影禁止。 (湯料430円)
1時間ゆっくり風呂に入った後は、お待ちかねのステーキです。
この12月にできた新店舗らしいですね。 +250円でカレーが食べ放題になってすごい。
食べ放題のカレーはこんな感じです。4杯食べてしまったのでとても反省しています。
(アンガスステーキ150g 980円+ご飯カレースープ食べ放題250円)
肉とカレーを手に入れたら当然こうなります。 美味しかったです。
スパワールドからの串カツが最高
スパワールドをご存じでしょうか。 知りたい人は次の記事を見ると速いです。
朝、出町柳駅に集合し、京阪でまっすぐ大阪へ。
昼ごはんは「ほまれ」に行った。
最近だとVTuberの日雇礼子さんが紹介してはったような。少なくとも中の人はやる夫スレで紹介していたはず。500円の天丼がすごい。物価の崩壊を感じる。 www.youtube.com
その後は、新世界で昔夢見たスマートボールをプレイ。未成年は遊べないんですよねこれ。 結果は惨敗。300円吸い込まれた。 友人は勝ちまくってとんがりコーンを手に入れていた。すごい。
虚無にお金を溶かしました(難しすぎる) pic.twitter.com/fXk9O7rlvF
— wass (@wass80) 2018年10月11日
お待ちかねのスパワールドは最高だった。平日の昼に行くとものすごく快適。10年ぶりだったが昔のまま変わらずという感じだった。塩サウナから滝湯までたっぷり2時間も堪能した。
そういえば昔行ったときもヨーロッパゾーンで、もう一方のゾーンには未だ行ったことがない。
スパワールドで何が便利かというと、入場料1000円以外 はクレカで払えるんですよね。しかも出場時払い。手首に巻いたやつで自販機から牛乳を買うことができる。
ただコインロッカーが100円硬貨式なんですよね。そこだけが悔やまれる。
そのあとは新世界で串カツですよ、串カツ。 とりあえずどこかに吸い込まれておけば串カツを食うことには困らないはず。
風呂上がりのゆったりした気分で京阪で京都に帰る。最高ですね。
ふらっと京都旅「一乗寺(詩仙堂・狸谷不動院)」
京都にずいぶん長くいるんですが、実はほとんど観光したことがありません。 そのため、最近また京都観光の気分が高まっています。
今回は紅葉の始まった時期に行った詩仙堂と狸谷不動院の写真たちです。
詩仙堂
京都に数多ある紅葉の名所の一つ。 名前の由来は三十六詩人を飾った詩仙の間によるらしい。*1
山荘に入るとすぐに目の前の庭を見ることが出来ます。この景色がメインです。
その後、庭に降りることが出来ます。この時は斑紅葉でしたが、これはこれで趣がある。
鹿威しを威すことができます。紅葉が乗っていて風流ですね。
八大神社
すぐ西、山の上側には八大神社があります。宮本武蔵がいました。
狸谷不動院
今回特に行きたかった所です。 詩仙堂の前の坂をさらにさらに登ると辿り着きます。 自転車を押して登りました。
入り口には狸の名前の通りたくさんのたぬきの信楽焼があります。
ここに来たかった理由は、『有頂天家族』の聖地巡礼というやつです。矢三郎のお母さんのパネルが建物の中にありました。
不動明王を祀る本殿がこちらです。中は撮影禁止でした。
この建物の不動明王を祀ってあるところは洞窟になっています。どういうことかというと、この建物を逆側から見たのが下の写真です。
このように崖に接しています。この崖にあった洞窟の目の前に本堂を建て、洞窟の中に不動明王を祀ったらしいです。
この日は特別にその洞窟の中まで入れました。人も居なかったのでその洞窟の静けさを味わうことができました。
百舌谷さんのMVが最高な件
2018-12-19に公開されたこのMVを見てほしい…
かっこよすぎる。
百舌谷(もずや)さんを知ったのはこの曲のMV。 www.nicovideo.jp
tilt-sixさんのchiptuneの名曲である。 このPVも百舌谷さんのものであった。 2013年の映像なんですが、今でもめっちゃ新しく感じてしまう。
2016年にもコラボをしていて、またこのMVがめちゃくちゃ力が入っている。
口周りの法線を反転して2次元的にしています。舌や目の影などはAEで処理しています。 pic.twitter.com/H6jB1idJLF
— 百舌谷 (@mozuya_) 2016年7月17日
3Dの技術面白いですね。
- アーティスト: tilt-six
- 出版社/メーカー: KarenT
- 発売日: 2014/08/22
- メディア: MP3 ダウンロード
- この商品を含むブログ (1件) を見る
アルコールって美味しいんですか?
お酒に弱いわけではない。お酒の影響はあまり受けない。ただちょっと内向的になってしまう。
お酒を楽しむ上でエタノールの美味しさをあまり理解していないのが問題らしい。ワインってもともとぶどうジュースが美味しいのに、その糖分をエタノールに変換して美味しさを減らしてしまってるように思う。でも、その発酵過程においてできる香りは嫌いではない。
同じことは日本酒にも言える。確かに日本酒の香りはいい匂いである。酒粕も好物である。ただ味はただのエタノールと残った糖分の味しかしないのでは?人間はいかに香りに騙されるかよく知っている分、味そのものはよくわからなくなる。
蒸留酒は更に難しい。味は殆ど失われて、香りしか残ってないんじゃないだろうか。ウイスキーにしたって、香りは楽しい。ただ味はエタノールでしかないように思えてしまう。
ビールが一番おいしさの評価ができない。飲みやすいか飲みにくいかでしか判断できない。苦いのが苦手でもないので、どんなビールでも飲むことはできる。
香りも含めて味という主張はわかる。しかし、その上でエタノールが味にプラスの働きをもたらしているか、わからない。 ごく少量のエタノールが清涼感を与えることはよく知られている。製菓に用いるラム酒などはそれを狙っている。だが、特に10%を超えるエタノールは雑味に感じてしまう。これは僕の舌が悪いんだろうか。
お酒が嫌いなわけでは決してなく、人のお酒をまずくしたいとも思っていない。カクテルを作ったことがあるが、楽しかった。ただ、お酒に対して真剣に考えると、このような雑念がどうしても振り払えない。
誰か美味しいお酒を教えてほしい。
かな配列を自分のSlackの発言から作る
このエントリーはKMC Advent Calendar 2018の14日目の記事です。
13日目の記事は以下の記事でした。
こんにちは。4回生になり、卒論の進捗に追われているwass80です。
キーボードの自作が流行っていますね。 自作は大変なので、今回は今あるキーボードで早く入力する方法を考えます。
tl;dr
- とりあえず手でかな配列を作って試す。
- 自分のSlackの発言から1-gram, 2-gramを求める。
- 自分にとって押しやすいかな配列を焼きなましで求める。
かな入力
ローマ字での入力ではなく、50音での入力です。 多くのキーボードに書かれている「JISかな」による入力が最も普及しています。 ローマ字入力では多くが1文字2打鍵(ha, ki)ですが、 かな入力では多くは1文字1打鍵(は, き)なので有利と考えられます。
しかし、キーボードに書かれている「JISかな」は、あまり最適化されていないように感じます。 なので、この配列をいじることで、より楽なかな入力について考えます。
かな配列の種類
いろいろなカナ入力配列 様々なかな配列についてはここが詳しいです。
- 用いるキーの数と配列
- シフトキーの数と種類
によって様々配列が提案されています。
3段配列、4段配列
使うキーの数は主に段数で示されます。 手元の「JISかな」を見るとわかるように、 「JISかな」は上から1段目数字のキーも使って全部で4段のキーを使っています。
有名な「親指シフト」は3段配列として知られています。
By udfoto - 自室にて撮影, CC 表示-継承 3.0, Link
更に極端な例だと2段配列や1段配列があります。 しかしこの場合、使えるキーが20個程度かより少ない数になります。 原則1キーに1かなを対応させる、かな配列の入力をするのには困難性が伴います。
シフトキー
たとえ4段配列にして、すべてのキーをかなに対応させても、キーは足りません。 入力したい文字は84ほどあります。
ぁあぃいぅうぇえぉおかがきぎくぐけげこごさざしじすずせぜそぞただちぢっつづてでとどなにぬねのはばぱひびぴふぶぷへべぺほぼぽまみむめもゃやゅゆょよらりるれろゎわゐゑをんー
そのため、配列のモードを切り替え、別の配列を入力する方法が必要になります。
前シフト、後シフト
入力したい文字の前(または後)にシフトキーを押すことで、別の文字を入力します。 お手元の「JISかな」では「゛」「゜」のキーが後シフトです。
「か」+「゛」=「が」
となります。
同時シフト
同時シフトは英字キーボードでのシフトキーと同じものです。 シフトキーが押されている間、別の文字が入力されます。
左右シフト
シフトキーが1つでは足りない場合は、シフトキーに左右で別の役割をもたせます。 「親指シフト」ではキーを押した時、同じ側のシフトか、反対側のシフトかで別の文字が入力されます。
とりあえず手でぽちぽち作った配列
かなの頻度表(=1gram)を見ながら、とりあえず手で配列を作りました。 参考: 漢直ノート ひらがな1-gram表
仕様
- Shiftキーは英数入力のためになるべく用いない。
- 濁点キーを前入力する。
- ゛+た=だ
- 濁点キーにより大小も反転する。
- ゛+っ=つ
- 二文字書かれているキーはその2文字が清濁の関係とみなす。
- ゛+め=ぬ
- Shift ゛+ は = ぱ
- 1+せ=8
考えたこと
- 頻度の高い文字を押しやすい上から3段目におく。
- 濁点キー「゛」が右手側に1つしかないため、清音を左手側に集めている。
- 左右のキーを交互に用いると速いため。
- 実は「JISかな」でもそのようになっている。
- 「が」は高頻度なので、単体でキーが存在する。
- 「っ」「ゃ」「ゅ」「ょ」が表の配列で小文字。
- 大文字より小文字のほうが頻度が高いため。
- あまりに使わない文字を清音の裏(濁点キーで入力)に押し込んだ。
試す
google IMEのローマ字テーブルを作りました。
問題点
- 配列を覚えるのが大変です。
- あまり頻度順に並んでいないように感じました。
- シフトキーは両側にないとキーの配列の左右の自由度が失われます。
真面目に最適化を考える
頻度順に並んでいないように感じる理由は、参考にした1-gramが自分の入力したコーパスではないからだと考えられます。 なので、サークルのSlackから自分の発言を抜き出し、自分のコーパスを作ります。 また、2-gram(連続2文字の頻度)を参考にして、 なるべく左右の手が交互に使われる配列を目指します。
python notebookはこちらです。 Keyboard配列 · GitHub
前処理
Slackから発言を取得する
slackclientを使いました。
コード
from slackclient import SlackClient slack_token = TOKEN sc = SlackClient(slack_token) def fetch_msg(page = 1, count = 100): try: res = sc.api_call( "search.all", query = "from:@wass80", count = count, highlight = False, page = page ) except e: print("Retry") sleep(10) yield from fetch(page, count) if "messages" not in res: print("missing", res) return res = res["messages"]["matches"] for r in res: yield r["text"] def fetch_all(count = 3, batch = 100): for i in range(1, count + 1): print("Fetch: %d" % i) yield from fetch_msg(i, batch) sleep(10) with open("msg.txt", "w") as f: for m in fetch_all(170): f.write(json.dumps(m) + "\n")
発言から記号を取り除く
def remove_symbols(s): s = re.sub(re.compile(r"```.+```", re.MULTILINE | re.DOTALL), " ", s) s = re.sub(r"<[^>]+>", " ", s) s = re.sub(r"`[^`]+`", " ", s) s = re.sub(r":[^:]+:", " ", s) return s remove_symbols("hoge<piyo>hoge`piyo`hoge```\npiyo\n```\nhoge\n:piyo:")
'hoge hoge hoge \nhoge\n '
読み方からひらがなに戻す
janomeを使いました。
from janome.tokenizer import Tokenizer tk = Tokenizer() def reading(s): res = "" for t in tk.tokenize(s): if t.reading != "*": res += t.reading else: res += " " return res reading("丸竹夷に押御池")
'マルタケエビスニ '
辞書にない単語は無視されますが、あまり問題にはならないでしょう。
1-gram, 2-gramを求める
1-gram
せっかくなので、手で作ったときに参考にした1-gramと比較しました。 文字が自分の1-gramです。丸が参考にした1-gramです。 青は参考より自分の頻度が多い。赤は逆です。
2-gram
これは、次の項目の1重キー、2重キーの計算結果が反映されています。
配列計算
配列の定義
- 1重キー(清音のみ)と2重キー(清濁)を定義します。
- 前シフトキー「゛」の後、2重キーは裏の文字になります。
- シフトキー「゛」の場所は固定。両側に配置。
2重キーの分解
逆に、表も裏もよく使うキーの場合、分解します。 「かが」→「か」「が」
2重キーの反転
裏のほうがよく使うキーの場合、反転します。 「ゆゅ」→「ゅゆ」
結果
1重キー(頻度順)
'イ', 'ン', 'ウ', 'カ', 'シ', 'ノ', 'ナ', 'タ', 'デ', 'テ', 'ル', 'マ', 'ニ', 'ガ', 'コ', 'ジ', 'リ', 'ョ', 'ア', 'ダ', 'ラ', 'オ', 'モ', 'レ', 'ヨ', 'エ', 'ヲ', 'メ', 'ミ', 'ワ', 'ム', 'ネ', 'ロ', 'ポ', 'プ', 'パ', 'ヅ', 'ペ', 'ピ', 'ヌ',
2重キー
[('キ', 'ギ'), ('ク', 'グ'), ('ケ', 'ゲ'), ('サ', 'ザ'), ('ス', 'ズ'), ('セ', 'ゼ'), ('ソ', 'ゾ'), ('チ', 'ヂ'), ('ッ', 'ツ'), ('ト', 'ド'), ('ハ', 'バ'), ('ヒ', 'ビ'), ('ブ', 'フ'), ('ヘ', 'ペ'), ('ホ', 'ボ'), ('ヤ', 'ャ'), ('ュ', 'ユ'), ('ー', '〜')]
1重キーの圧縮
1重キーと2重キーの合計がキーの数より多いので、 1重キーの裏に1重キーを置くことで2重キーにします。 これによって「゛」+「シ」=「ワ」など、多少理不尽なことが起こりますが、諦めます。
配列の計算
- キーの場所をどこにするか。
- 圧縮された1重キーの場合、裏に回ったキーの場所をどこにするか。 この2つがパラメータとなります。
配列のコスト
3種類のパラメーターをキーの場所ごとに定義しました。
self.press = [100, 10, 0, 20][y] + [1, 0, 0, 0, 1, 1, 0, 0, 0, 10, 20, 30][x] self.finger = [1, 2, 3, 4, 4, 5, 5, 6, 7, 8, 8, 8][x] self.hand = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1][x]
- pressは押しにくさを表す重み。
- fingerは押す指。
- handは押す手。
最終的な配列のコストは:
- 各キーのpressとその1_gramから次の値を合計する。
- front = 1_gram * press * 1gram_w (表キーの押しにくさ)
- back = 1_gram * press * 1gram_w * back_w (裏キーの押しにくさ)
- 2_gramから次の値を合計する。
- 同じ指を連続で使うなら finger = 2_gram * same_finger_w
- 同じ手を連続で使うなら hand = 2_gram * same_hand_w
それぞれ重み(*_w)
は何度か焼きなましして適当に決定しました。
焼きなまし
適当に生成した配列にこの2つの演算を適用します。 上で定義した配列のコストが小さくなるように焼きなましをします。
初期生成
上が表、下が裏です。
ー | ュ | ヤ | ホ | ヘ | ブ | ヒ | ハ | ト | ッ | チ | ソ |
セ | ス | サ | ケ | ク | キ | イ | ン | ウ | カ | シ | ノ |
゛ | ナ | タ | デ | テ | ル | マ | ニ | ゛ | @ | ガ | コ |
ジ | リ | ョ | ア | ダ | ラ | オ | モ | レ | ヨ | エ | ヲ |
〜 | ユ | ャ | ボ | ペ | フ | ビ | バ | ド | ツ | ヂ | ゾ |
ゼ | ズ | ザ | ゲ | グ | ギ | メ | ミ | ワ | ム | ネ | ロ |
ポ | プ | パ | ヅ | ペ | ピ | ヌ | - | - | - | - | - |
- | - | - | - | - | - | - | - | - | - | - | - |
- コスト: 21.629534099116793,
- 'hand': 0.39129205368876097,
- 'finger': 0.4639484594384564,
- 'front': 18.760037452391433,
- 'back': 2.0142561335981406
焼きなまし後
エ | ソ | ー | レ | セ | ブ | ヤ | ュ | ヲ | ホ | ヒ | ヘ |
デ | ス | ク | ル | ッ | テ | タ | ハ | ナ | コ | モ | チ |
゛ | ン | イ | シ | ウ | ノ | ト | カ | ゛ | @ | ジ | ア |
ラ | リ | キ | ニ | オ | ョ | マ | ガ | ダ | サ | ヨ | ケ |
- | ゾ | 〜 | - | ゼ | フ | ャ | ユ | - | ボ | ビ | ペ |
ヅ | ズ | グ | ペ | ツ | ヌ | プ | バ | パ | - | - | ヂ |
ポ | ミ | メ | ワ | ム | ロ | ド | ネ | - | - | - | - |
- | ピ | ギ | - | - | - | - | - | - | ザ | - | ゲ |
- コスト: 15.252237067009535,
- 'hand': 0.3088283757887919,
- 'finger': 0.20433342691029233,
- 'front': 13.840631230449489,
- 'back': 0.8984440338609608
できた配列を試す
- Google IMEのローマ字テーブルとして出力して試しました。
- わかったこととして、4行配列は手の動きが大きすぎてやはり面倒です……。
- 指のもつれとかは起こりにくいのかもしれないです。
- ただ、キーを全然覚えられる気がしなかったので諦めてしまいました。
phoenix配列
やはり4段配列は手を1段目に持っていくのが面倒です。 手が動かない配列はないかと探すと、phoenix配列が良さそうでした。
phoenix配列は2段配列のアルファベット方式です。 サイトにはgoogle IME用のテーブルが用意されているので、手軽に試すことが出来ます。
phoenix Real Time Input Method
漢字直接入力も対応しているらしいですが、流石に覚えられない自信があります。
wass-phoenix配列
せっかく自分の1-gramを求めたので、この配列も自分に最適化しました。
結局配列を作っても、それを覚えるコストが高すぎて、全然使いこなせません……。 なにかいい方法はあるのでしょうか。