流沙河鎮

情報技術系のこと書きます。

q言語基礎_4_Dictionary(Tableの誕生)

# 以下は2020年頃に執筆した過去ブログのアーカイブです。現在メンテしておらず、一部の情報が古い可能性があります

へんてこデータベースもどき「kdb」と、知る人ぞ知るその実装言語「q」の基本を解説する本シリーズ。
本連載の目的はq言語の基本的な情報、実務で用いられる実践的なテクニックを日本語で分かりやすく提供することである。細かい情報や定義を知りたい時は適宜「q for mortals」及び「q Tips」を参照されたい

q for mortals
https://code.kx.com/q4m3/2_Basic_Data_Types_Atoms/

q Tips
https://www.amazon.com/Tips-Fast-Scalable-Maintainable-Kdb-ebook/dp/B00UZ8OMME

Dictionary

Dictionaryは一対一に紐づくkey, valueのマップで、それぞれがListである。各keyの値はユニークである必要があり、それぞのListはsimple listとgeneral listどちらの場合もある。*1
DictionaryはDictionaryであると同時に、2つのListのペアとして捉えられる点がポイントである。

Dictionaryの宣言

keyとなるListとvalueとなるlistの間に!(bang)を置くことで宣言される。
keys!values

q)d:1 2 3!`hoge`moge`fuga
q)d
1| hoge
2| moge
3| fuga

valueは以下のようにして取出し可能。

q)d[1]
`hoge
q)d[2]
`moge

存在しないkeyを与えた場合単にnullが返る。

q)d[1111]
`

?(Find)オペレータを用いることで、value→keyの逆引きも可能。

q)d?`hoge
1

key、valueのListはそれぞれ以下の関数で取出せる。

q)key d
1 2 3
q)value d
`hoge`moge`fuga
q)count d
3

※一般的な言語における辞書型との相違点として、qにおいては中身のカバレッジが同じであっても、内部のListの順序が異なるDictionaryは等価と見做されない。

q)d1:1 2 3!`hoge`moge`fuga
q)d2:1 3 2!`hoge`fuga`moge
q)d1~d2
0b
Dictionaryの操作

存在するkeyにvalueアサインした場合はamend、存在しないkeyにvalueアサインした場合は追加となる。

q)d:1 2 3!`hoge`moge`fuga
q)d[1]:`puga
q)d
1| puga
2| moge
3| fuga
q)d[4]:`poga
q)d
1| puga
2| moge
3| fuga
4| poga

cutを使用することで要素を削除できる。オペランド”_”でも同じ効果が得られ、両者は恐らく等価である。
なんでこんな紛らわしい予約語作ったんだ…

q)d
1| hoge
2| moge
3| fuga
q)1 2 cut d
3| fuga
q)(enlist 1) cut d / 削除する対象のkeyはListで渡さないとダメ
2| moge
3| fuga
q)1 2 _ d /アンダースコアでも同じ効果が得られるのだが、使いどころは謎だ…
3| fuga
q)(enlist 1) _ d
2| moge
3| fuga
Dictionaryの応用

Dictionaryを関数の引数として与えることで、各valueに関数を適用することが出来る。

q)d2:`hoge`fuga`hoga!1 2 3
q)d2
hoge| 1
fuga| 2
hoga| 3
q)neg d2
hoge| -1
fuga| -2
hoga| -3
q)d2=20
hoge| 0
fuga| 0
hoga| 0
q)d2*20
hoge| 20
fuga| 40
hoga| 60

,(join)同士のjoinも可能で、keyが重複した場合右側のDisctionaryが優越する。

q)d1:`hoge`fuga`hoga!1 2 3
q)d2:`hoge`pega!10 20
q)d1,d2
hoge| 10
fuga| 2
hoga| 3
pega| 20

,(join)の亜種として^(Coalesce)というのもある。
keyが重複した場合右側が優先される点も含めて挙動はjoinとほぼ同じだが、こちらは右側のListのvalueがNULLであった場合のみ左側の値で上書きされる。

q)d1:`hoge`fuga`hoga!1 2 3
q)d2:`hoge`fuga`hoga!10 0N 30
q)d1^d2
hoge| 10
fuga| 2
hoga| 30
Tableの誕生

本連載を読み進める中で、一部の読者は幾らかのフラストレーションを感じてきたのではないかと想像している。
オレはデータベース勉強したくてqに入門したのに、何時まで経ってもTable出てこねーじゃねぇか!、と。
その辛抱も今日で終わりだ。ListとDictionaryを修めた今、読者諸兄はq/kdbにおけるTableを召喚する準備が出来ている。
先ずは完成形をお見せしよう。

q)flip `country`area`population!(`Japan`Germany`Kazakhstan;`Asia`Europe,`$"Central Asia";126.5 83.02 18.28)
country    area         population
----------------------------------
Japan      Asia         126.5
Germany    Europe       83.02
Kazakhstan Central Asia 18.28

どうだろう、データベースのテーブルとして馴染み深いフォーマットに見えるのではないだろうか?
宣言部は一見面食らうかもしれないが、よく見ればflip以外は目新しい要素はないはずだ。
先ずはflipなしで見てみよう。これは`country`area`populationをkeyとするDictionaryで、それぞれがvalueとして配列を保持している。
key'country'の値はJapan Germany Kazakhstan、key'area'の値はAsia Europe Central Asia....という具合だ。

q)`country`area`population!(`Japan`Germany`Kazakhstan;`Asia`Europe,`$"Central Asia";126.5 83.02 18.28)
country   | Japan Germany Kazakhstan
area      | Asia  Europe  Central Asia
population| 126.5 83.02   18.28

flipは、与えられたデータのx軸とy軸を反転する関数である。

q)((1 2 3 4);(5 6 7 8))
1 2 3 4
5 6 7 8
q)flip ((1 2 3 4);(5 6 7 8))
1 5
2 6
3 7
4 8

これを先ほどのDictionaryに適用するとどうなるだろう?

q)flip `country`area`population!(`Japan`Germany`Kazakhstan;`Asia`Europe,`$"Central Asia";126.5 83.02 18.28)
country    area         population
----------------------------------
Japan      Asia         126.5
Germany    Europe       83.02
Kazakhstan Central Asia 18.28

Voilà!*2
これこそがkdbにおけるテーブルなのだ。type関数の返り値もテーブル型を意味する98を示す。

q)Country:flip `country`area`population!(`Japan`Germany`Kazakhstan;`Asia`Europe,`$"Central Asia";126.5 83.02 18.28)
q)type Country
98h

テーブル型のデータはq-sql*3によってクエリを掛けることが出来る。

q)select from Country
country    area         population
----------------------------------
Japan      Asia         126.5
Germany    Europe       83.02
Kazakhstan Central Asia 18.28
q)select from Country where country=`Japan
country area population
-----------------------
Japan   Asia 126.5
q)select from Country where population > 100
country area population
-----------------------
Japan   Asia 126.5
q)select from Country where population < 100
country    area         population
----------------------------------
Germany    Europe       83.02
Kazakhstan Central Asia 18.28

どうだろう、一気にデータベーステーブルを扱っている感が出てきたのではないだろうか?
ここまで読んでいただいた皆様にはお分かりの通り、kdbにおけるテーブルというのは、Listを要素として持つDictionaryをflipしたものに過ぎず、他のデータベースで扱われるような、カプセル化された特別なデータ型というわけではない
従って、テーブルデータはqの実装を通じて、自由自在に変形、加工することが出来る。
これこそが筆者がkdbを”データベースみたいなもの”と呼ぶ所以であり、kdbというプロダクトを特徴づける特色なのである。
また、テーブルの各カラムは元々がDictionaryのKey/valueであることから、kdbは本質的に列指向データベースであると言える。

詳しいTableの挙動については、Tableの章(次の次ぐらいかな?)で詳述する。

*1:厳密にはkeyが重複するListも宣言できるのだが、keyからvalueを取出す際に2つ目以降は無視される

*2:ヴォ・エ・ラ!英語話者がテンション上がった時によく叫ぶフレーズ。ジャジャーン!ぐらいの意味

*3:今後の章で詳述する