私が歌川です

@utgwkk が書いている

ソートマージ結合法を実装してみた

関係データベースの本を読んでいて,結合質問を処理するアルゴリズムのソートマージ結合法について知ったのですが,動作例だけでアルゴリズムが書いてなかったので,実装をしました.

結合質問とは

テーブルRとSがあって,それぞれ行数がMとNであるとします.みなさんもこういう SQL クエリを書いたことがあると思います.

SELECT R.hoge, S.fuga FROM R
INNER JOIN S ON R.id = S.hoge_id

「テーブル R と S のデータのうち, R.id = S.hoge_id を満足する行の組を結合したもののみを返せ」という意味のクエリです. たとえば,テーブル R が以下のようになっているとします.

id hoge
1 "a1"
2 "a2"
3 "a3"
4 "a4"

また,テーブル S が以下のようになっているとします.

hoge_id fuga
1 "c1"
2 "c2"
2 "c3"
3 "c4"

これらのテーブルに対して上のクエリを実行した結果は次の通りになります.

hoge fuga
"a1" "c1"
"a2" "c2"
"a2" "c3"
"a4" "c4"

ところで,これ以降はテーブルの全てのカラムを取得するときのみを考えることにします.

総当たり法

さてこのクエリに答えるにはどうすればよいかを考えるのですが,すぐ思いつくのは総当たり法でしょう. したがって,たとえばこのような実装が考えられます.

def brute_force(R, S):
    result = [] # 回答を格納するリスト
    for r in R: # R の全ての行 r について
        for s in S: # S の全ての行 s について
            if r[-1] == s[0]: # (r の結合属性の値) == (s の結合属性の値)
                result.append(r + s) # r と s を結合したものを回答に加える
    return result # 回答を返す

しかしながらこの方法は計算量が O(MN) となるので,テーブルの行数が大きくなると実用的な時間でクエリに答えることができなくなります*1

ソートマージ結合法

ソートマージ結合法 (Sort-merge join) は,総当たり法よりも高速に結合質問に答えるためのアルゴリズムです.実装したコードを下に示します*2

def sortmerge(R, S):
    M, N = len(R), len(S) # テーブルRとSの行数
    p1, p2, p3 = 0, 0, 0 # 3つのポインタ.p1 は R,p2 と p3 は S の先頭を指す.
    result = [] # 回答を格納するリスト
    while p1 < M and p3 < N: # p1 が R の終端を指す,もしくは p3 が S の終端を指すまで
        if p2 >= N: # p2 が S の終端を指しているなら
            p1 += 1 # p1 を進める
            p2 = p3 # p2 を p3 に戻す
        else:
            if R[p1][-1] < S[p2][0]: # (R[p1] の結合属性の値) < (S[p2] の結合属性の値)
                p1 += 1 # p1 を進める
                p2 = p3 # p2 を p3 に戻す
            elif R[p1][-1] == S[p2][0]: # (R[p1] の結合属性の値) == (S[p2] の結合属性の値)
                result.append(R[p1] + S[p2]) # R[p1] と S[p2] を結合したものを回答に加える
                p2 += 1 # p2 を進める
            else: # (R[p1] の結合属性の値) > (S[p2] の結合属性の値)
                p3 += 1 # p3 を進める
                p2 = p3 # p2 を p3 に合わせる
    return result # 回答を返す

計算量は,RとSが共にソート済みであるとすると O(M + N) となります*3

まとめ

ソートマージ結合法を知りました.

アルゴリズムの紹介をするならせめて疑似コードを書いてほしい.

*1:実際にはインデックスが貼られていることが多いので,もう少し高速にはなると思われます.

*2:R と S が昇順にソートされているという仮定をしています.

*3:ソートが必要な場合でも O(MlogM + NlogN) となります.

SQLite で ORDER BY RANDOM() を使わずに10連ガチャを実現する

tl;dr

  • WITH RECURSIVE で主キーの乱数列を作って絞り込むと爆速

ナイーブな実装

SQLite でガチャを実装しようとすると通常次のようになるかと思われます.

SELECT * FROM idols ORDER BY RANDOM() LIMIT 10;

よく知られているように,このクエリは非常に重いです.45万件ほどのデータに対してこのクエリを投げると,結果が返ってくるまでに待たされることが多いです.

WITH 句で乱数列を生成する

WITH句でとりあえず仮想的な一時テーブルのようなものを作っていると考えてください.rowid はテーブルの主キーとします.

WITH random_id(id)
AS (
  SELECT ABS(RANDOM() % (SELECT MAX(rowid) FROM idols))
  UNION ALL SELECT ABS(RANDOM() % (SELECT MAX(rowid) FROM idols)) FROM random_id
)
SELECT DISTINCT id FROM random_id LIMIT 10;

これによって,0〜MAX(idols.rowid) の範囲をとる乱数列が生成できます.

rowid の乱数列で絞り込む

あとはこれを組み込んでみましょう.

WITH random_id(id)
AS (
  SELECT ABS(RANDOM() % (SELECT MAX(rowid) FROM idols))
  UNION ALL SELECT ABS(RANDOM() % (SELECT MAX(rowid) FROM idols)) FROM random_id
)
SELECT * FROM idols JOIN (SELECT DISTINCT id AS rid FROM random_id LIMIT 1000) ON idols.id == rid LIMIT 10;

こうして爆速の10連ガチャを実装することができました.よかったですね*1

注意

  • 主キーが連番になってないときの動作は保証しません
  • 追記したのですが, WHERE id IN (SELECT DISTINCT id FROM random_id LIMIT 100000) による判定では偏りが出てしまいます.これは random_id の取得数を減らすか,もしくは上の修正されたクエリのように JOIN を使うことによって解決できるようです

おまけ: rowid について

SQLite のテーブルには必ず rowid という INTEGER PRIMARY KEY AUTOINCREMENT があるので*2,主キーを設定するの忘れてた……というときにも使うことができます. この主キーは隠しカラムであり,これを元にして主キーを再設定することができます.

*1:部内のガチャシステムが40倍高速になりました

*2:実は INTEGER の主キーはこれを参照している

今日の夢日記

全くどういうことなのか分からないけど,「オブジェクト指向の部屋」「大麻常用ツイッタラー」というのがめちゃくちゃ印象に残っている.

英語でのコーディング面接の感想

機会があって英語でのコーディング面接を受けることがあったので,終わってから感じたことを書き留めておく.

感じたこと

話せる語彙が少ない

読める/聞ける語彙はそこそこあって,ゆっくり話してもらえれば聞き取れるし,意味も分かるけど,自分が話を組み立てるという段階になって分かることとして,話せる語彙が思ったより少ない,ということがある. また,使う単語はそんなに難しいものではないけどイディオムとして知らないので話せない,みたいな表現もけっこうあって,話せることがどんどん減るのがつらかった.

情報工学的な英語表現ができなかった

「時間計算量は入力長を N として N の2乗に比例します」という表現が言えなくて complexity is order square N と言っていた. また,書いたコードの説明をするにあたって,解答コードを Python で書いたのだが,Python のコードをそのまま読み下すような説明をしてしまい,かなり時間を消費してしまった. 擬似コードに落とし込んだような表現ができればもっとスムーズに進められただろうなと思う.

その他

  • はきはき喋る
  • もうちょっと事前準備をして話のネタを用意しておく.前日にちょっと予想しただけで終わった話題がそのまま出た
  • コードが合ってたかどうか不安になってきた*1*2
  • コーナーケースの指摘をさらっと流してしまったのはよくなかった

まとめ

見返してみると,「英語は難しい」では済まされないレベルの英語しか話せなかったし,その結果,日本語なら難なくできるであろうことが大きく制限されてかなり不利になってしまったように思う. 普段使ってないからだとか,ちゃんと勉強してないからだとか言われると,確かにそうとしか言えない. 言語のために普段できることができなくなるのは非常に悔しいのでなんとかしたいですね. 院に進学するならどうせ TOEIC とか必要になるだろうし,ぼちぼちやっていく必要がある.

*1:あとでちゃんと書いたところそれっぽい挙動をしているので,書き間違えてなければセーフ

*2:これは試験後のよくあるあれなので,あまり気にしてはいけないと思う

サークルで『ゼロから作るDeep Learning』勉強会を始めた

表題の通りの勉強会を始めた.隔週土曜日に部室に集まって輪読するという形式でやっている.4/22に始めて,明日3章をやる予定.

追記: サークルのブログに記事を書いた.

kmc.hatenablog.jp

この本を積んでいたことと,ディープラーニングが何なのか結局わかっていなかったので勉強してみようと思ったことがきっかけで,ディープラーニングというか機械学習全般の知識はほとんどないし,数理統計の単位は落としてる.サークル内で募ってみたところ思ったよりも人が集まってしまった.

本について,ぱっと見た感じでは,ガチガチの理論を説いていくのではなく,初学者でもちゃんと読んで動かしてみれば雰囲気をつかめるように書いてある,という印象.今のところ*1は難しすぎるということもなく読めている.高校数学でドロップアウトしなかった人なら読めるのではなかろうか.

サークル内の勉強会を主催するのは初めてのことなので,手探りでいろいろやってみていて,フィードバックを得ているという状態.以下に学びを共有しておきます.

学び

  • 1章は Python 入門ということで本に沿ってざっくりやったが,時間が余って何しようかというときに,Python の文法や挙動のあれやこれやについてメンバーの指摘を受けて見ていくみたいなことをしていて,初学者によくなかった.気がつく人たちは気がつくのはそれはそうで,しかし初学者もいる中でやって置いてけぼりを喰らわせてしまうのはよくないので今後気をつける.
  • 論文紹介と称してツイッターで流れてきたディープラーニングを用いた論文をチラ見する,みたいなことをしたが,今読んでも……という気はするし,そもそも論文紹介というのは初学者がやるものではなさそう,という指摘を受けた.ツイッターで流れてくるようなものは,arXivに張り付いているような人が書いているものが大半なので,論文紹介とするならもっと枯れたものを紹介したほうが勉強になりそうと言われた.
  • 今回は90分時間をとったところ余ってしまって,次回以降どうしようかと考えたけど,見てみると3章以降から歯ごたえが出てきている様子だった.「このまま2章ずつでいきましょう」というのを突き通さなくてよかった……死んでしまう.
  • Numpy 配列の挙動を見た部員から「Rのベクトルですね」という感想が出ていた.次元の違うもの同士の計算をしてもよしなにやってくれるが,これが吉と出るか凶と出るかは,私はRのクソバイトをしていないのでまだ分からない.
    • X[X > 15] という書き方ができるのすごいですね.
  • Windows に anaconda をインストールすると python コマンドが取られて,マジか……となった.
  • Pythonic な世界と numpy の世界があるんだなあという印象.先の配列添字にいろいろ指定できるところもそうだし,そのうち考えがコンフリクトしそうだなーという気がする.とりあえず本に従うようにしたい.
  • Google スライドで上/下付き文字を駆使してパーセプトロンを書いたけど,数が多くなってくるとしんどいし,数式メインになってきたらやっぱりTeXを使うべきだろうか?
  • 勉強会を部内Ustreamで配信するという試みがなされていて*2,遠隔受講が可能になっている.@wass80 に感謝.

KMCM

KMCでは,みんなで集まって読書会をしてみたい部員を募集しています*3.入部制限はありません.詳しくは入部案内ページをごらんください.

www.kmc.gr.jp

入部したくなったら気軽に @KMC_JPなどに連絡してください.遠隔入部も可能です

*1:3章終わりまで.

*2:福岡にいる部員から受講したいという申し出があって,高速に準備された.

*3:この決まり文句は伝統のようなものなので,ようするにパソコンを使った活動がしたい人なら誰でも大歓迎です.

GW2日目なので動物園に行ったしカクテルを飲んだしアニデレを見た

京都市動物園

主にペンギン見とけもフレグッズがメインだった. ペンギンはぼうっと立っている姿と泳いでいる姿の両方がいいと思う.写真はありません.

カクテル

いろいろ飲んだので忘れた. 6種類ぐらいは飲んでいたと思う.

アニデレ

知り合いの家でアニデレを見た.12話(合宿)から.

11話までは以前に見ていたので,ちょうど転換期となるようなところ*1から見始めるという感じだった. 私はデレステから入った人間なのでアニデレの文脈をまったく知らなかったけど,これでようやく全部回収できたという感じ. 24話のエンディングで S(mile)ING! が流れるのはやっぱりずるい*2. 2クール目後半での,卯月のアイデンティティへの苦悩をこれでもかと見せられた後に流れてくる曲は一味変わって聞こえる.

ところで私は26話がいちばん好きです,理由は安心できるから*3. あとシン撰組ガールズで卯月が人を切った後にサイコパスっぽくなってるところが良かった.

*1:というかクールの変わり目ですね.

*2:これは褒めています.

*3:これは常々思っていることなんだけど,安心できないという理由で長めのアニメをなかなか見ないというのがあるような気がする.

GW1日目なので比叡山を越えた

ルート

やったこと

叡山ケーブルに乗った

春休みに失敗していたので,やっと乗れたという感じ. ケーブルというのは上りと下りを同期して動かす必要があって,ようするに滑車に2つの車をつるしているような状態.

叡山ロープウェイに乗った

こっちはみなさんが想像するようにロープウェイで,3分ぐらいと乗車時間は短かった.

比叡山の山道を歩いた

ロープウェイの駅からバスで坂本ケーブルまで行けたけど,せっかくなので歩いた. けっこういっぱい歩いている人がいたけど,案内板によれば30分かかるので少し気合が必要. 途中で歴史的建造物が見れる.

延暦寺を見た

全部は見てないけど道中にあったものだけ. ご朱印を書いてもらったり,灰になると文字が出てくる線香を焼いたりした.

琵琶湖を見た

山の上からちゃんと見たのはたぶん初めてで,確かに海に間違えられてたんだろうなーと思った.

坂本ケーブルに乗った

こっちもケーブルだが,途中でトンネルがあったり,合流点があったりして面白かった.

京阪石山線のシステムを知った

石山線は本線と連結でない上に,いろいろな事情が重なっているので,同じ京阪という名前でも浜大津から淀屋橋に行くのは難しい. 山科駅といっても,地下鉄の山科駅京阪山科駅はまったく違う駅としてカウントされているらしく,直感的でなかった.

まとめ

全体的にそこそこの値段だった. 比叡山を越えて滋賀に入って帰ってくるというルートはずっとやりたかったので満足している. ゴールデンウィークには街を離れましょう.