Finding all paths in a directed acyclic graph structure with CPAN Graph module

非循環有向グラフで、ある頂点から頂点までの全経路を探します。
This script finds all paths from a vertex to another.

(注意点: 循環構造があると無限ループします)
NOTE: cannot be used with cycles.

use strict;
use warnings;
use Graph::Directed;
use Data::Dumper;

sub find_all_paths {
    my ($from, $dest) = @_;
    return [[$dest]] if $from eq $dest;
    return undef unless $g->is_reachable($from, $dest);
    my @paths;
    foreach my $edge ( $g->edges_from($from) ) {
        my $child_paths = find_all_paths($edge->[1], $dest);
        if (defined($child_paths)) {
            foreach my $child_path (@$child_paths) {
                push(@paths, [$from, @$child_path]);
            }
        }
    }
    return \@paths;
}

# usage

my $g = Graph::Directed->new;
$g->add_edges(qw(
    b a
    c a
    d a
    e a
    f a
    g a
    h b
    i c
    j h
    k h
    l i
    m l
    n i
    o d
    o t
    p j
    q j
    q r
    q s
    r l
    r n
    s o
));

my $paths = find_all_paths('q', 'a');
print Dumper($paths);

outputs:

$VAR1 = [
          [
            'q',
            's',
            'o',
            'd',
            'a'
          ],
          [
            'q',
            'r',
            'n',
            'i',
            'c',
            'a'
          ],
          [
            'q',
            'r',
            'l',
            'i',
            'c',
            'a'
          ],
          [
            'q',
            'j',
            'h',
            'b',
            'a'
          ]
        ];

非技術的な視点からのOpenIDの「危険性」

OpenIDなんて聞いたこともないおそらく大多数のネットユーザーからすると、偶然たどり着いたサイトでいきなりmixiのログインとか求められたら


「なにこれ、なんでmixiのパスワードとか聞いてくんの? 怪しくない? パスワードなんて入れたら何か変なことされちゃうんじゃないの?」


とか思うだろうな、と思った。


OpenIDを知ってる人からすれば全然問題ないのだけど。

ポテトとコーラ

チーズバーガーが無性に食べたかったので、近くのマクドナルドでいつものようにセットを頼んだ。
チーズバーガーを食べ終わってみたら、ポテトとコーラは余計だったことに気付いた。
そう考え出すと、いつもの好物のはずのポテトがあまり美味しく感じられなかった。物はいつも通りの揚げ加減塩加減のはずなのに。これほどまでに美味しくないポテトは初めてだった。


そうだ、俺が食べたかったのはチーズバーガーだったんだ。
バーガーはセットで頼むのが当たり前だといつの間にか思っていたけど、チーズバーガーを単品で頼めばよかったんだ。

DjangoでShift-JISの携帯サイトを作る

Djangoのバージョンは1.0.2を使っています。
まず、settings.pyでDEFAULT_CHARSETをcp932に指定しておきます。
ここでShift_JISを指定してしまうと「〜」などの文字を出力しようとした時にUnicodeErrorが出てしまいます。

DEFAULT_CHARSET = 'cp932'

このままだとレスポンスのHTTPヘッダが Content-Type: text/html; charset=cp932 となってしまい良くないのでContent-TypeをShift_JISに付け替えます。middleware.pyを作ってその中でMiddlewareクラスを定義しておきます。

class SJISMiddleware(object):
  def process_response(self, request, response):
    response['Content-Type'] = 'text/html; charset=Shift_JIS'
    return response

特定のファイルはContent-Typeを変えたくない場合はif文で振り分けるなどすると良いでしょう。Content-Typeがtext/htmlの場合だけ付け替えるというのも良いかもしれません。


さらにContent-Lengthも付与するようにMiddlewareクラスを作っておくと良いと思います。

class ContentLengthMiddleware(object):
  def process_response(self, request, response):
    if not response.has_header('Content-Length') and response.content:
      response['Content-Length'] = len(response.content)
    return response

あと機種判別のため uamobile も使うケースが多いと思います。とても素晴らしいパッケージですね。

class UserAgentMobileMiddleware(object):
  def process_request(self, request):
    request.device = None
    try:
      request.device = detect(request.META)
    except Exception, e:
      print "Can't detect device: %s" % e

最後に上で作った3つのMiddlewareをsettings.pyで有効にします。こんな感じでしょうか。

MIDDLEWARE_CLASSES = (
    'django.middleware.common.CommonMiddleware',
    'some.project.middleware.ContentLengthMiddleware',
    'some.project.middleware.SJISMiddleware',
    'some.project.middleware.UserAgentMobileMiddleware',
)

some.projectの部分はmiddleware.pyの配置箇所によって適宜置き換えてください。

ちなみにMIDDLEWARE_CLASSESで指定したクラス群は、リクエストの時は上から順にprocess_request()関数が処理され、レスポンスの時は下から順にprocess_response()関数が処理されます。

PHPでユニコードエスケープ(unicode_encode, unicode_decode代替)

PHP6からは unicode_encode() 関数と unicode_decode() 関数が追加されるらしいのですが、PHP5やPHP4でユニコードエスケープをしたい時のために。

// UTF-8文字列をUnicodeエスケープする。ただし英数字と記号はエスケープしない。
function unicode_decode($str) {
  return preg_replace_callback("/((?:[^\x09\x0A\x0D\x20-\x7E]{3})+)/", "decode_callback", $str);
}

function decode_callback($matches) {
  $char = mb_convert_encoding($matches[1], "UTF-16", "UTF-8");
  $escaped = "";
  for ($i = 0, $l = strlen($char); $i < $l; $i += 2) {
    $escaped .=  "\u" . sprintf("%02x%02x", ord($char[$i]), ord($char[$i+1]));
  }
  return $escaped;
}

// Unicodeエスケープされた文字列をUTF-8文字列に戻す
function unicode_encode($str) {
  return preg_replace_callback("/\\\\u([0-9a-zA-Z]{4})/", "encode_callback", $str);
}

function encode_callback($matches) {
  $char = mb_convert_encoding(pack("H*", $matches[1]), "UTF-8", "UTF-16");
  return $char;
}

// 使用例
$str = "東京";

// ユニコードエスケープする
$decoded = unicode_decode($str);
echo "$decoded\n"; // 「\u6771\u4eac」が出力される

// UTF-8に戻す
$encoded = unicode_encode($decoded);
echo "$encoded\n"; // 「東京」が出力される

Movable TypeのPerl APIを使って移行作業

やりたいこと

(1) 移行元のMovable TypeのデータをYAMLでダンプ
    ↓
(2) YAMLを移行先サーバに転送
    ↓
(3) 移行先のMovable TypeYAMLをインポート

ここまでやります。

投稿者をダンプ&インポートする

まず全投稿者をダンプしてみます。
以降の説明では Movable Type を /var/www/html/mt3-cgi にインストールしたものと仮定しています。別のディレクトリにインストールした場合は読み替えてください。


すべての投稿者をダンプするスクリプト: dump_authors.pl

#!/usr/bin/perl
use strict;
use warnings;
use lib qw(/var/www/html/mt3-cgi/lib);
use MT;
use MT::Author;
use YAML::Syck;

# カレントディレクトリがMTのインストールディレクトリではない(mt-config.cgiがない)場合は
# mt-config.cgi へのパスを指定する
my $mt = MT->new(
  Config => '/var/www/html/mt3-cgi/mt-config.cgi'
);

# load() の () 内に検索条件を指定しなければ全件取得
my @authors = MT::Author->load();

# YAMLでダンプする
print Dump @authors;

移行元サーバでこのスクリプトを実行します。

% ./dump_authors.pl > authors.yaml

この authors.yaml を移行先サーバに転送します。


次に、投稿者をインポートするスクリプト: import_authors.pl

#!/usr/bin/perl
use strict;
use warnings;
use lib qw(/var/www/html/mt3-cgi/lib);
use MT;
use MT::Author;
use YAML::Syck;

# カレントディレクトリがMTのインストールディレクトリではない(mt-config.cgiがない)場合は
# mt-config.cgi へのパスを指定する
my $mt = MT->new(
  Config => '/var/www/html/mt3-cgi/mt-config.cgi'
);

# 標準入力(STDIN)からYAMLデータを読み込む
my $yamlstr = join('', <>);

# オブジェクトに戻す
my @authors = Load($yamlstr);

foreach my $author (@authors) {
#  $author->save で投稿者が登録されます
  $author->save or die $author->errstr;
}

さきほどの authors.yaml を読み込ませてスクリプトを実行します。

% ./import_authors.pl < authors.yaml

この要領です。そんなに難しくないですよね?

カテゴリをダンプ&インポートする

カテゴリは MT::Category を使います。
ダンプするスクリプトはこんな風になります。

# 全カテゴリを取得
my @cats = MT::Category->load();

# ダンプ
print Dump @cats;

インポートするスクリプトも同様です。

# YAMLをロード
my $yamlstr = join('', <>);
my @cats = Load($yamlstr);
print Dumper(@cats);

foreach my $cat (@cats) {
  # $cat->save でカテゴリが登録されます
  $cat->save or die $cat->errstr;
}

ほかにもいろいろありまっせ

同様にブログ一覧はMT::Blogで移行できます。使い方は MT::Blogのドキュメント にあります。
このほかにもテンプレートやプラグインデータなど一通りPerl APIが用意されていて大変お手軽です。
参考: Movable Type オブジェクト・リファレンス

大量のデータをインポートするとき

データが大量にあるときはYAMLのロードでたくさんメモリ食っちゃって大変です。
なので1件ずつ逐次パースしてインポートしていくといいと思います。こんな感じで

my $buf;
while (<>) {
  if ($_ eq "--- \n" && $buf) {
    my $data = Load($buf);
    &process_data($data);
    $buf = '';
  }
  $buf .= $_;
}

sub process_data {
  my $data = shift;

  # $data をインポートするなどの処理
}

結論

Movable TypePerl API は大変よくできていて素晴らしいです。

Movable Typeに大量のエントリを投入テスト中

仕事でMovable Type 3.35に10万件ぐらいのエントリを投入中。
まだ5,300件なのでだいぶ時間かかる。しかもだんだん遅くなってる気がする。
CustomFieldsとかプラグイン使ってるのでAPI使わずWWW::Mechanizeで自動投入してる。あまりMT使ったことないのでもっといい方法があるのかもしんない。
一応ダイナミック・パブリッシングにはしたけど、1つのブログに大量にエントリ入れたらどうなるかパフォーマンス見てみます。