2009年12月27日日曜日

MySQL でレコードのランダム抽出をするには

ORDER BY RAND() で OK。

ただ、全レコードに対してランダムな数値を与えてインデックスなしでソートするのでそれなりのコストがかかる。

この場合の計算量はどうなんだろう。ソートの実装がどうなっているのかは知らないので確かなことは分からないけれど、LIMIT をつけずに全レコードをランダムソートするならば計算量はよくて O(N log N)程度のはず。LIMIT で K 件(K < log N)を抽出する場合は、愚直にやっても O(KN)あればできる。

よって、1件ずつランダム抽出していくような場合は大して問題にならないが、リストとしてまとめてランダムに並んだレコードを取り出したい、というような場合にはあまり効率的でない。
そういった場合には事前にインデックスつきのフィールドに RAND() を書き込んでおいて、取得したいときにそのフィールドで ORDER BY してやったほうがいいはず。

2009年12月26日土曜日

Django の ModelForm / ModelFormSet まとめ

ModelForm の扱いが分かってきたのでまとめ。

なぜ ModelForm を使うのか (普通の Form との違い)

普通のフォームはデータベースの構成とは独立しているので自由に入力フォームを作成することができる反面、データベースの構造を反映したフォームを作成する際には、モデルの記述とほぼ同じ内容をもう一度書き下す必要が出てくる。これは冗長であるし、またモデルの変更をフォームに反映しなければならない点で保守性が低い。
きれいに構成された Web アプリケーションであればデータの入出力はデータベース上での生成、変更、削除にほぼ対応しているわけで、そのようなシーンにおいて、モデルを反映した入力を実現するためにモデルから半自動的にフォームを生成するものが ModelForm である。

ModelForm の定義

モデルフォーム内のサブクラス "Meta" の属性として各種の定義を書く。
  • model 属性: フォームのもととなるモデル
  • fields 属性: モデルの中でこのフォームに含めたいフィールド
各々のフィールドがどのようなフォームで表示されるか (テキストなのかチェックボックスなのかプルダウンメニューなのか等々)は、モデルで指定したフィールドのタイプに基づいて適当なものが選択される。
とはいえ実際の運用においては表示の仕方を変えたいことも多々ある(最もよくあるのは hidden にしたい場合など)ので、これは変更することが可能。
これは Meta 内ではなく、モデルフォーム自身の属性として記述。

ModelFormSet

一度に複数のレコードを編集する場合、同じモデルフォームをそのまま複数並べるわけにはいかない。(同じ name を持つ input 要素が複数作成されて、submit されてもどれとどれを組み合わせるのか分からなくなるから。)
FormSet はこの問題を解決するためのもの。ひとまとめに扱う input 要素ごとに id (フォームセットの内部的なもの。HTML の id 属性ではない) を作成してフォームの name 属性を適宜変更、送信されたデータをきちんとグループ分けして一つ一つのフォームを切り分けてくれる。
ModelFormSet は、上述の Form と ModelForm の関係ように、Model を反映した FormSet。

ModelFormSet の定義

modelformset_factory に、ベースにしたいモデルを渡す。
フィールドの表示の種類を変更するためには、意図する表示を定義した ModelForm を予め定義しておき、引数に "form=モデルフォーム名" を追加してやればよい。
ここで、上述の引数として渡したモデルフォームにおいて、fields 属性が定義されていても、新たに作成される ModelFormSet はそれらを無視して全てのフィールドに関してフォームを作成する点に注意。
これを抑制するには、modelformset_factory の 引数に "fields=フィールド名リスト" を与える。

データ受信後の処理

POST でデータを受け取ってからは、送信されたデータをデータベースに書き込むことになる。
ここで、既存のデータを変更する場合には注意すべき点がある。
基本的に、ModelForm のデータを保存する場合はモデルフォームオブジェクトの save メソッドを利用する。
これは新規作成(SQL でいうと INSERT)でも上書き(SQL でいうと UPDATE)でも同じで、Django はそれらを自動的に使い分ける。
使い分けの基準は、"保存しようとしているデータの主キー値と同じ主キー値を持つレコードがあれば UPDATE、それ以外は INSERT"となっている。
よって、既存のデータを UPDATE するためには、必ず主キーをフォームの中に含めなければならない。
また、一部のフィールドのみを用いたモデルフォームの場合、除外したフィールドが必須であった場合、その値を与えなければ is_valid が False となり保存できない。
このような場合は、受信後にフォームインスタンスを作成する際に、POST データとともにもとのデータベースレコードを渡してやることで、フォームに含まれているフィールドはその値を、そうでないフィールドはデータベースの値を用いて、不足するデータを補ったフォームオブジェクトを作成することができる。
渡し方は、モデルフォームの場合は引数 instance、モデルフォームセットの場合は引数 queryset。

2009年12月25日金曜日

ruby in HTML

ruby と言っても言語の方ではありません。HTML の ruby (ルビ)タグです。
そもそも ruby というタグが HTML のセマンティクス的に許されるかどうかというのは十分議論の余地があるとは思いますが、文字列に近接して付記すべき情報があるのも事実。
で、ここ12時間くらいどうすべきか迷ったことをメモ。

ruby タグの使い方

  1. ルビを振りたい部分を <rb> でマークアップ
  2. ルビとして表示したい文字列を上記 <rb> に隣接する <rt> 内に記述
  3. <rb> と <rt> を <ruby> でマークアップ
例えば
田んぼと<ruby><rb>案山子</rb><rt>かかし</rt></ruby>ののどかな風景

と書くと
田んぼと案山子かかしののどかな風景
となります。

ブラウザ互換性の問題

となります、と書きましたが、上記のサンプルは Internet Explorer もしくは Google Chrome 以外のブラウザではきちんと表示されなかったことと思います。というのは主要なブラウザのうち上記の2つ以外には ruby タグが実装されていないためです。そのため互換性を実現するにはそれなりの workaround が必要になります。
(正直なところそういう見苦しい HTML は書きたくないのですが、差し迫って必要なのだから仕方がない。ちなみに CSS 3 には ruby が含まれているようなのでそのうち他のブラウザでも利用できるようになるのでしょう。)

Firefox における ruby

この手の話はあらゆるブラウザに対応することを目指すときりがなくなるので、今回のサイトが研究室内の他のメンバーに見てもらうためのものであることを踏まえて Firefox できちんと表示できればそれでよしとすることにします。
手っ取り早い方法としては、そのためのアドオンを導入する手があります。しかしながら今回のサイトはどちらかというと周囲にお願いして見てもらうものであるので、できるだけ利用のためのハードルを低くしておくためにもユーザ側では作業をさせたくありません。なので CSS で対処したいと思います。
調べたところ、こちらのサイトに書かれている CSS がベストな解となりそうです。
簡単に言うと、ruby を inline-table、rb を table-row、rt を table-header-group とするというものです。
ただ、そのまま上記のタグの属性を変更すると、もともと ruby をきちんと表示できているブラウザにおいて表示が崩れるので、span に class を設定してクラスセレクタで CSS を設定するのがよさそうです。
先ほどの例をこの方法でマークアップすると以下のようになります。
<style>
#ruby{
display: inline-table;
vertical-align: bottom;
}
#rb{
display: table-row;
}
#rt{
display: table-header-group;
}
</style>
田んぼと
<span class="ruby">
<span class="rb">
案山子
</span>
<span class="rt">
かかし
</span>
</span>
ののどかな風景

そしてこれを表示するとこのようになります。
田んぼと案山子かかしののどかな風景

意図した通りになっているのではないでしょうか。
上記の場合、ルビのサイズを地の文よりも小さくするために rt の font-size を50%に、また周囲の語との垂直方向の位置会わせのために ruby の vertical-align を bottom に設定しています。
とりあえずこれだと Firefox 以外に Chrome、Safari でもきちんと表示できるようです。

さらなる問題

さて、ここまででルビを振ることはできた訳ですが、ちょっとした問題に遭遇しました。
<rb> 内に、間に空白を挟んだ複数の <span> を配置すると、空白が反映されません。
SPAN1 SPAN2RUBY

中身だけであれば
SPAN1 SPAN2

と、きちんと間に空白が表示されるので、おそらく table-row の中に直接 span を複数配置しているのが何らかの影響を与えているものと思われます。
これは、上記の複数の span を、さらに一つの span にまとめてやることで回避できます。
SPAN1 SPAN2RUBY
あまり詳しくないので確定的なことは言えないのですが、おそらく table-row の中には table-cell に相当する要素が必要だということなのではないかと推測しています。

以上、とても胸を張れないバッドノウハウでした。

2009年12月22日火曜日

Django の分からないところ

調べてみたけど分からなかったことリスト。(随時更新)
知っている方、教えていただけると本当に助かります。
あるモデルの一部のフィールドのみ編集可能にしてそれ以外は表示だけさせたい

複数のフィールドを持つモデルの値のうち一部を書き換える場合。例えばこんなかんじで。オブジェクトとそれをインスタンスとして作ったフォームオブジェクトをコンテキストに突っ込めばテンプレートから参照はできるけれど、なんだか冗長だし、複数のインスタンスを一度に編集できるようにしたい場合などはテンプレートにリストが2つ渡って対応付けしたりとややこしくなる。
フォームオブジェクトから value を抜き出して表示することがテンプレートからできれば万事解決なんですが、そういう方法ってないのだろうか。

複合キーを扱いたい

とりあえず現時点では扱えないらしい?
複合キーにすべきフィールド以外に主キーをたてておく。
データモデリング的によろしくないが実用上は複合キーで検索してリレーションには主キーを使えばまあ問題ないか。
(追記) モデルオブジェクトの Meta 内で unique_together(フィールド名リスト) で指定可能だった。

ModelForm での is_validがよくわからない

何をチェックしているのかよくわからない。モデルで指定した「省略可能」 (blank=True) とか 「ヌル値許容」 (null=True) とかの条件にフォーム内データが合致しているか否かをチェックしているのだろうとは思うけれど、暗黙の主キーである id フィールドがフォームに入ってると問答無用で False を返される。なぜ?
追記: 主キーの値の重複が不正と見なされている。下に書いたように主キーを指定した場合は update になるので問題ないはずだが、なぜか invalid となっている。なぜ?
追記: ここに答えがあった。つまり
  1. 主キーは POST パラメータではなく URL で渡す
  2. そこから取得した主キーをもとにして既存レコードのインスタンスを取得する
  3. そのインスタンスと POST パラメータを一緒に渡してインスタンスを作り直す
が正解の模様。

Django は insert と update を勝手に呼び分ける

save が呼ばれたデータの Primary Key が既にデータベースに存在すれば update、なければ insert を実行する。Primary Key が Python 的に False である場合は update する。例えば Primary Key の値が入ってないデータは insert になるので、Primary Key 以外に unique 制約を持つデータなどは、既存データのつもりでうっかり Primary Key のフィールドを入れずに save したりすると insert されて unique 制約に引っかかる、とかありそう。

メモリリーク?

大量データ(300万レコードほど)をデータベースに突っ込むために Django のモデルをインポートして作成したスクリプトをコマンドラインで走らせているとどんどんメモリを食う。やっていることはレコードごとに関数を読んで、その中で新たにモデルのインスタンスをローカル変数に代入して save するというだけ。関数から抜けた後にどこかの時点で GC にかかることを期待していたのだけれど、どうもそうなってはくれない模様。

2009年12月6日日曜日

あす楽がよかった

3週間ほど前から友人達と今日鍋をする約束をしていました。
せっかくの機会だから外で食べると高いカニでも食べようということで楽天の店に注文していたのですが、3日前に「その商品が受注可能でないのに誤って掲載されていたのでキャンセルとします」という連絡が来たとのことで、その夜に急遽あす楽対応の店に切り替えて注文し直しました。

あす楽だと正午までの受注は翌日着になるものの、旭川の店とのことできちんと夕飯に間に合うのか半信半疑だったのですが、驚いたことに朝の10時に着きました。どうやって発送するのか知らずに注文していたのですが、航空便で届くんですね。

Amazon のお急ぎ便でもかなりすごいと思っていたのですが、まさか冷凍の食品を航空便で送ってもらえるなんて夢にも思っていなかったので驚きでした。
コスト的に大丈夫なのか気になりますが、利用する側としてはありがたいサービスです。

2009年12月3日木曜日

Google の日本語インプットメソッドを使ってみた

なにやら Google が日本語のインプットメソッドを作ったらしい。

Google Japan Blog: 思いどおりの日本語入力 - Google 日本語入力
http://googlejapan.blogspot.com/2009/12/google_03.html

ほぼ間違いなく世界最多のテキストデータを持っているであろう Google だからこういう利用法を思いつくのはごく自然な流れですよね。
関わっているのも数々の機械学習ライブラリや MeCab で知られる工藤拓さんはじめオープンソースの日本語インプットメソッドの開発者とくれば期待も高まります。

で、使ってみているところです。

まだあまり長く使ってないので大した感想は書けないんですが、特に不満を感じない完成度であることは分かりました。
とりあえず、スペースで変換、タブでいわゆる補完になっている模様。この辺は ATOK と同じですね。
長い慣用句、しかも最近の言い回しが補完候補に出てきたりして、手元で Google Suggest が動いてるような感覚です。