2013/01/09(水)中村、ウォーキングのリハビリ開始

2013/01/09 23:07
左膝を手術した中村がウォーキングを開始しました。 [【西武】左膝手術おかわり6分間歩行訓練 - プロ野球ニュース : nikkansports.com](http://www.nikkansports.com/baseball/news/f-bb-tp0-20130109-1069918.html) 文字数制限のある見出しだからといって、「左膝手術おかわり」だと手術を失敗して再手術したみたいじゃねーか、というツッコミはおいておくとして。 もうウォーキングができるようになったということは、経過は順調だと考えて良さそうですね。まずはホッとしました。 ただ、順調だからといって急いで復帰を目指したりしないで、一歩一歩着実に先に進んで欲しいと思います。無理をして何日か、あるいは何週間か早く復帰したところで、その後の選手生命を縮めてしまうようでは意味がありません。私は回復の具合によっては最悪今年は復帰できなくても仕方がないくらいには思っていますので、くれぐれも焦らずに復帰を目指してもらいたいと思います。

2013/01/08(火)Djangoからtextsearch_jaを利用する

2013/01/08 20:32
Djangoからtextsearch_jaを呼び出す場合、素直にやるとORマッパーの恩恵を受けることができず、SQLのWHERE句をゴリゴリ手書きすることになります。 毎回毎回これではちょっと面倒なので、searchメソッドを呼び出すことで全文検索ができるModelを作ってみました。作ったとは言っても、一から作ったわけではなく、Django snippetsにあったtsverctorを操作するモデルにちょこっと手を入れただけですが。手を入れた部分はコメントで補足してあります。
[crayon lang="python"] # -*- coding: utf-8 -*- """ django.db.models.Model を拡張して、PostgreSQLのtsvector型に対応したモデルとマネジャを提供します。 http://djangosnippets.org/snippets/1328/ で公開されているコードを一部修正して使わせていただきました。 以下、原文のコメントです。 -------------------------------------------------- Support for full-text searchable Django models using tsearch2 in PostgreSQL. An example: from search import SearchableModel, SearchManager from django.db import models class TestModel (SearchableModel): name = models.CharField( max_length=100 ) description = models.TextField() # Defining a SearchManager without fields will use all CharFields and TextFields # objects = SearchManager() # You can pass a list of fields that should be indexed # objects = SearchManager( fields=('name','description') ) # You may also specify fields as a dictionary, mapping each field to a weight for ranking purposes # see http://www.postgresql.org/docs/8.3/static/textsearch-features.html#TEXTSEARCH-MANIPULATE-TSVECTOR objects = SearchManager( fields={ 'name': 'A', 'description': 'B', } ) # Create some test data. By default, the index field is automatically updated when save() is called. TestModel.objects.create( name='Model One', description='Hello world, this is a test.' ) TestModel.objects.create( name='Model Two', description='Testing, testing, one two three.' ) # You can force an index update to all or some instances: TestModel.objects.update_index() TestModel.objects.update_index( pk=1 ) TestModel.objects.update_index( pk=[1,2] ) # Perform a search with no ranking TestModel.objects.search( 'hello' ) # Perform a search that ranks the results, orders by the rank, and assigns the ranking # value to the field specified by rank_field TestModel.objects.search( 'test', rank_field='rank' ) """ from django.db import models class VectorField (models.Field): def __init__( self, *args, **kwargs ): kwargs['null'] = True kwargs['editable'] = False kwargs['serialize'] = False super( VectorField, self ).__init__( *args, **kwargs ) def db_type( self, connection=None ): # Django1.4対応のため、仮引数connectionを追加 return 'tsvector' class SearchableModel (models.Model): """ A convience Model wrapper that provides an update_index method for object instances, as well as automatic index updating. The index is stored as a tsvector column on the model's table. A model may specify a boolean class variable, _auto_reindex, to control whether the index is automatically updated when save is called. """ search_index = VectorField() class Meta: abstract = True def update_index( self ): if hasattr( self, '_search_manager' ): self._search_manager.update_index( pk=self.pk ) def save( self, *args, **kwargs ): super( SearchableModel, self ).save( *args, **kwargs ) if hasattr( self, '_auto_reindex' ): if self._auto_reindex: self.update_index() else: self.update_index() class SearchManager (models.Manager): def __init__( self, fields=None, config=None ): self.fields = fields self.default_weight = 'A' self.config = config and config or 'pg_catalog.english' self._vector_field_cache = None super( SearchManager, self ).__init__() def contribute_to_class( self, cls, name ): # Instances need to get to us to update their indexes. setattr( cls, '_search_manager', self ) super( SearchManager, self ).contribute_to_class( cls, name ) def _find_text_fields( self ): """ Return the names of all CharField and TextField fields defined for this manager's model. """ fields = [f for f in self.model._meta.fields if isinstance(f,(models.CharField,models.TextField))] return [f.name for f in fields] def _vector_field( self ): """ Returns the VectorField defined for this manager's model. There must be exactly one VectorField defined. """ if self._vector_field_cache is not None: return self._vector_field_cache vectors = [f for f in self.model._meta.fields if isinstance(f,VectorField)] if len(vectors) != 1: raise ValueError( "There must be exactly 1 VectorField defined for the %s model." % self.model._meta.object_name ) self._vector_field_cache = vectors[0] return self._vector_field_cache vector_field = property( _vector_field ) def _vector_sql( self, field, weight=None ): """ Returns the SQL used to build a tsvector from the given (django) field name. """ if weight is None: weight = self.default_weight f = self.model._meta.get_field( field ) return "setweight( to_tsvector( '%s', coalesce(\"%s\",'') ), '%s' )" % (self.config, f.column, weight) def update_index( self, pk=None ): """ Updates the full-text index for one, many, or all instances of this manager's model. """ from django.db import connection # Build a list of SQL clauses that generate tsvectors for each specified field. clauses = [] if self.fields is None: self.fields = self._find_text_fields() if isinstance( self.fields, (list,tuple) ): for field in self.fields: clauses.append( self._vector_sql(field) ) else: for field, weight in self.fields.items(): clauses.append( self._vector_sql(field,weight) ) vector_sql = ' || '.join( clauses ) where = '' # If one or more pks are specified, tack a WHERE clause onto the SQL. if pk is not None: if isinstance( pk, (list,tuple) ): ids = ','.join( [str(v) for v in pk] ) where = " WHERE \"%s\" IN (%s)" % (self.model._meta.pk.column, ids) else: where = " WHERE \"%s\" = %s" % (self.model._meta.pk.column, pk) sql = "UPDATE \"%s\" SET \"%s\" = %s%s;" % (self.model._meta.db_table, self.vector_field.column, vector_sql, where) cursor = connection.cursor() cursor.execute( sql ) # cursor.execute( "COMMIT;" ) # TransactionMiddleware対応のため削除 # cursor.close() # TransactionMiddleware対応のため削除 def search( self, query, rank_field=None, rank_normalization=32, use_web_query=False ): # web_query対応のため引数追加 """ Returns a queryset after having applied the full-text search query. If rank_field is specified, it is the name of the field that will be put on each returned instance. When specifying a rank_field, the results will automatically be ordered by -rank_field. For possible rank_normalization values, refer to: http://www.postgresql.org/docs/8.3/static/textsearch-controls.html#TEXTSEARCH-RANKING """ # web_query対応のため修正 # ts_query = "to_tsquery('%s','%s')" % (self.config, unicode(query).replace("'","''")) if use_web_query: to_tsquery_string = "to_tsquery('%s',web_query('%s'))" else: to_tsquery_string = "to_tsquery('%s','%s')" ts_query = to_tsquery_string % (self.config, unicode(query).replace("'","''")) where = "\"%s\" @@ %s" % (self.vector_field.column, ts_query) select = {} order = [] if rank_field is not None: select[rank_field] = 'ts_rank( "%s", %s, %d )' % (self.vector_field.column, ts_query, rank_normalization) order = ['-%s' % rank_field] return self.all().extra( select=select, where=[where], order_by=order ) [/crayon]
呼び出す側では、searchメソッドに条件やrankに利用するフィールド名を渡すことで全文検索が実行できます。
[crayon lang="python"] result = Spam.objects.search(query='俺達 炎上', rank_field='rank', use_web_query=True) [/crayon]
`use_web_query`に`True`を渡すことで、textsearch_jaのweb_queryの機能を使うことができ、スペース区切りの各文字列をAND条件として扱ったり、`OR`で結ぶことでOR条件として扱うことができます。web_queryの詳細は[公式サイト](http://textsearch-ja.projects.pgfoundry.org/textsearch_ja.html#web_query)で。 また、searchメソッドだけではなく、別の条件を組み合わせることもできます。
[crayon lang="python"] result = Spam.objects.search(query='検索条件', rank_field='rank', use_web_query=True).filter(enable=True) [/crayon]
これでだいぶスマートにtextsearch_jaを扱うことができるようになりました。まぁ、95%以上のコードは俺じゃなくて[dcwatsonさん](http://djangosnippets.org/users/dcwatson/)が書いたんだけど。

2013/01/07(月)若獅子たちの始動

2013/01/07 22:46
ライオンズのルーキーたちが続々と始動しています。 * [【西武】ドラ1増田「新人王」の誓い - プロ野球ニュース : nikkansports.com](http://www.nikkansports.com/baseball/news/f-bb-tp0-20130106-1068681.html) * [【西武】ドラ3金子「けがをしない体を」 - プロ野球ニュース : nikkansports.com](http://www.nikkansports.com/baseball/news/f-bb-tp0-20130107-1069069.html) * [【西武】高橋厳選マンガ200冊と入寮 - プロ野球ニュース : nikkansports.com](http://www.nikkansports.com/baseball/news/f-bb-tp0-20130106-1068683.html) * [【西武】ドラ5佐藤「ダイヤのA」と入寮 - プロ野球ニュース : nikkansports.com](http://www.nikkansports.com/baseball/news/f-bb-tp0-20130106-1068682.html) * [【西武】水口「安眠マイ枕」持参で入寮 - プロ野球ニュース : nikkansports.com](http://www.nikkansports.com/baseball/news/f-bb-tp0-20130106-1068680.html) 増田、金子、高橋、佐藤、水口……よし、全員揃っているな。

2013/01/06(日)OS X Mountain Lionにtextsearch_jaの環境を構築する

2013/01/06 18:34
PostgreSQLから呼び出せる形態素解析を利用した日本語の全文検索エンジン、[textsearch_ja](http://textsearch-ja.projects.pgfoundry.org/textsearch_ja.html)を利用させてもらっているのですが、依存しているプログラムのバージョンがtextsearch_jaの開発当初と異なっているため、環境の構築に苦労した部分がありましたので、手順をメモ的に残しておきたいと思います。 作業当時とPostgreSQLの最新バージョンが異なりますが(作業時9.2.1、現在9.2.2)、そこだけ読み替えれば上手く行くはずです。 #### Java Javaランタイムがインストールされていない場合、作業の途中でインストールの要求をされると思います。前もって入れる必要はありません。 #### Xcode [App Store](https://itunes.apple.com/jp/app/xcode/id497799835?mt=12)よりインストールしてください。 #### Command Line Tools XCodeを起動し、メニューの[Xcode]-[Preferences...]から、[Downloads]タブの[Components]を選択し、[Command Line Tools]の横の[Install]ボタンを押下してインストールしてください。 20130106 01 #### Homebrew ターミナルから以下のコマンドを入力してインストールしてください。ただし、これは2013年1月6日現在のコマンドなので、念のため[Homebrewのサイト](http://mxcl.github.com/homebrew/)を確認して最新のコマンドを確認してください。(ページの一番下にコマンドが書いてあります)
[crayon nums="false" striped="false"] ruby -e "$(curl -fsSkL raw.github.com/mxcl/homebrew/go)" [/crayon]
インストール終了後、以下のコマンドを入力します。
[crayon nums="false" striped="false"] brew doctor [/crayon]
`"Your system is raring to brew."`と表示されればインストールは成功です。続いて、以下のコマンドを入力し、HomebrewとFormulaを更新してください。
[crayon nums="false" striped="false"] brew update [/crayon]
#### 環境変数 /usr/local/bin を最優先にしたいので、.bash_profile に以下の記述を追加してください。
[crayon] export PATH=/usr/local/bin:$PATH [/crayon]
#### PostgreSQL ターミナルから以下のコマンドを入力してインストールしてください。  
[crayon nums="false" striped="false"] brew install postgresql [/crayon]
インストール終了後、以下のコマンドを入力してデータベースクラスタを初期化します。
[crayon nums="false" striped="false"] initdb /usr/local/var/postgres -E utf8 [/crayon]
PostgreSQLを自動起動にする場合、以下の3つのコマンドを入力します。毎回手動で起動する場合はこの操作は必要ありません。
[crayon nums="false" striped="false"] mkdir -p ~/Library/LaunchAgents cp /usr/local/Cellar/postgresql/9.2.1/homebrew.mxcl.postgresql.plist ~/Library/LaunchAgents/ launchctl load -w ~/Library/LaunchAgents/homebrew.mxcl.postgresql.plist [/crayon]
#### pgAdmin GUIでDBを操作したい場合、[ダウンロードページ](http://www.pgadmin.org/download/macosx.php)からpgAdminをダウンロードし、インストールしてください。 #### DB作成 pgAdminを利用して(あるいはコマンドラインから)DBを作成します。このとき、データベースの定義は以下のように設定してください。 * Template : template0 * コレーション : C * 文字型 : C 20130106 02 #### MeCab ターミナルから以下のコマンドを入力してインストールしてください。
[crayon nums="false" striped="false"] brew install mecab brew install mecab-ipadic [/crayon]
#### textsearch_ja [ダウンロードサイト](http://pgfoundry.org/frs/?group_id=1000298)からソースコードを入手します。(textsearch_ja-9.0.0.tar.gz) 解凍後、Makefile に下記の1行を追加してください。  
[crayon] LDFLAGS = "-L/usr/local/lib" [/crayon]
Makefile 修正後、以下のコマンドを入力してインストールしてください。
[crayon nums="false" striped="false"] make USE_PGXS=1 sudo make USE_PGXS=1 install [/crayon]
インストール後、 /usr/local/Cellar/postgresql/9.2.1/share/postgresql/contrib/textsearch_ja.sql を編集します。 "LANGUAGE 'C'" という記述をすべて小文字、クォーテーションなしに修正 してください。 * 修正前: LANGUAGE 'C' * 修正後: LANGUAGE c 修正後、以下のコマンドを入力し、関数をPostgreSQLに登録します。
[crayon nums="false" striped="false"] psql -f /usr/local/Cellar/postgresql/9.2.1/share/postgresql/contrib/textsearch_ja.sql [DB名] [/crayon]
以上で設定は完了です。

2013/01/05(土)むかーしむかし

2013/01/05 21:02
昨日、いわゆる「古参」と呼ばれるくらい昔からやっているQMAプレイヤーさんたちと飲んできました。 その中で「昔はみんなブログをやってたよねー」という話が出たのですが、今もそのまま更新が続いているところはほとんどありません。「昔」が指している00年代中盤辺りはちょうど一般的にもブログブームだったということもあり、[まとめWiki](http://www16.atwiki.jp/database_qmabloger/)が作られるほどの数のブログが存在しました。ただ、そのうちにみんなmixiで身内向けの日記を書くようになり、今ではTwitterに活動の場を移している人がほとんどです。 ちなみに、このブログは数少ない更新が続いているQMAプレイヤーのブログの一つです。このブログ自体はQMAを始める前から書いてはいますが、QMA2以降は廃人プレイヤーの日記としての立ち位置でもあったので、プレイヤーの視点から見たQMAの過去を知る貴重な資料になっているのではないかと思います。いや、わりと真剣に。ただ、こんなに更新が続くのなら、もうちょっと記録的なこともちゃんと残しておけば良かったなーと後悔している面もありますが。 QMAプレイヤーがブログを書かなくなったのと同期するように、QMAプレイヤーのブログを見るという文化自体も廃れつつあるようで、このブログも一時期に比べるとアクセス数が減ってきています。4月1日だけはアクセス数のゼロが一つ増えるので、そういう意味ではまだそれなりに期待されてるのかなーと勝手にうぬぼれていますがw 最近、このブログの内容が野球やプログラミングに寄りつつあるのは、もちろん忙しくてQMAのプレイペースが落ちているということもあるのですが、新しい分野、元気のある分野を開拓して読者を確保するためでもあったりします。もちろん、QMAをやめるつもりがない以上、QMAについて書くのをやめるつもりもありませんけれど。 まぁ、なんだ。Twitterは確かに楽だし、周りの反応が早いから楽しいけどさ。みんなもブログ書こうぜ。まとまった形で文章を残すのも意味があるものだからさ。