sudo

む。

  • /etc/sudoers の編集には visudo を使いましょう。文法チェックもしてくれる
  • /etc/shadow の編集には sudo vipw -s としましょう。まぁインストール直後だと問題ないだろうけど
  • su を禁止するのはいいけど、この設定だと sudo -u user -s とか、sudo sh -c su とやってしまえば通過できてしまう。まぁ、su を禁止することで類似する行為をするな、というメッセージにはなるので、まったくの無意味だとは言わないが。sudo で実行したコマンドはログに残るし

つうか、半端に一部のユーザに su 出来るようにするのは変でないかい?postgres の権限で実行したいなら

% sudo -u postgress commands

で十分。root と postgres だけ許可したいなら

Cmnd_Alias SU = /bin/su
%unoh ALL = (root, postgres) ALL, !SU

な感じだろう。もっと真面目にやるなら

Cmnd_Alias SHELLS = /bin/sh, /bin/bash, /bin/csh, /bin/tcsh, /usr/local/bin/zsh
Cmnd_Alias SU = /bin/su
%unoh ALL = (root, postgres) ALL, !SU, !SHELL

かな。まぁ

% cp /bin/bash $HOME/bin
% sudo $HOME/bin/bash

とかで通過されるんだけど、そこまでやるとログであからさまだし、sudo を使う人間はほぼ確実に関係者になるから、見つけ次第きつく説教すればいいという話も有る。結局、運用ポリシーと設定のバランスの問題かも。
あと、su じゃなくて sudo を使うのは誤った操作を防ぐのもあるけど、実行したコマンド全てがログに残ることの方が大きいんじゃないかと思う。

京都大学コーパスのパーズ

コードを見る限り cabocha -f1 としたときの出力の解析みたいだけど、あれは CaboCha 特有のフォーマットじゃなくて京都大学コーパス由来のフォーマットだと思います!
で、ちょっと真面目に解析するように書き直してみた。

  • やっていることは analyze じゃなくて parse だと思うので関数名を変更
  • dictionary じゃなくてちゃんとオブジェクトに入れる
  • 文字列では対象が大きいときに何かと不便なので、適当に iterable なオブジェクトを受け取るように。
    • file object でも list でも tuple でも generator でも何でも可。
#!/usr/bin/env python

import sys

class Token(object):
    __slots__ = 'surface read base pos ctype cform ne'.split(' ')

    def __init__(self, **kwd):
        for name, value in kwd.iteritems():
            if name in self.__slots__:
                setattr(self, name, value)
            else:
                raise KeyError()

    def __str__(self):
        return '\t'.join(getattr(self, name) for name in self.__slots__)

class Chunk(object):
    __slots__ = 'id link rel score head func tokens'.split(' ')

    def __init__(self, **kwd):
        self.tokens = []
        for name, value in kwd.iteritems():
            if name in self.__slots__:
                setattr(self, name, value)
            else:
                raise KeyError()

    def __str__(self):
        s = '* %d %d%s %s/%s %f\n' % \
            (self.id, self.link, self.rel,
             self.head, self.func, self.score)
        for token in self.tokens:
            s += str(token)
            s += '\n'
        return s
    
class Tree(object):
    __slots__ = ['chunks']

    def __init__(self, chunks):
        self.chunks = chunks

    def __str__(self):
        s = ''
        for chunk in self.chunks:
            s += str(chunk)
        s += 'EOS\n'
        return s
            
def parse(source):
    chunks = []
    current_chunk = None

    for line in source:
        line = line.rstrip('\r\n')

        if len(line) == 0 or line.startswith('#'): continue
       
        if line == 'EOS':
            # end of sentence
            yield Tree(chunks)
            chunks = []
        elif line.startswith('* '):
            # start of chunk
            (cid, link_rel, head_func, score) = line[2:].split(' ')
            cid = int(cid)
            link = int(link_rel[:-1])
            rel = link_rel[-1]
            head_func = head_func.split('/')
            head = int(head_func[0])
            func = int(head_func[1])
            score = float(score)

            current_chunk = Chunk(id = cid, link = link, rel = rel,
                                  head = head, func = func, score = score)
            chunks.append(current_chunk)
        else:
            # token
            (surface, read, base, pos, ctype, cform, ne) = line.split('\t')
            token = Token(surface = surface, read = read, base = base,
                          pos = pos, ctype = ctype, cform = cform, ne = ne)
            current_chunk.tokens.append(token)

if __name__ == '__main__':
    import fileinput
    import locale
    import optparse

    default_encoding = locale.getpreferredencoding()

    parser = optparse.OptionParser()
    parser.add_option('-i', '--input', dest='input_encoding', metavar='INPUT_ENCODING', default=default_encoding)
    parser.add_option('-o', '--output', dest='output_encoding', metavar='OUTPUT_ENCODING', default=default_encoding)
    (options, args) = parser.parse_args()
        
    source = (line.decode(options.input_encoding) for line in fileinput.input(args))
    for tree in parse(source):
        for chunk in tree.chunks:
            for token in chunk.tokens:
                print token.surface.encode(options.output_encoding)

Quicksort が遅い理由

クイックソートは、その性質上、再帰の最後の方になってくるとほとんど同じ要素同士を比較することになってしまうので、文字列配列の場合、最後の方の比較のコストがかなり大きくなってしまうので、それが影響しているのではないかと。

む。そうかも。
まぁ、なんにしても Multikey Quicksort が結構速いっすね。いや MSD Radix Sort も同じぐらいの性能が出るけど。
つうか、一口に文字列ソートといっても、どんなデータをソートするかによるわけだけど。DNA の塩基配列なんかだと MSD Radix Sort のが強いかも。

接尾辞配列

ぬお。日本語版 WikipediaSuffix Array の項目が。接尾辞配列なんていっている人いるのか、とか思ったけど検索したらそこそこ使われているし。
で、どうも英語版の翻訳らしんだけど、日本語訳がすごいことになっているね。

The easiest way to construct a suffix array is to use an efficient comparison sort algorithm. This requires O(nlogn) suffix comparisons, but a suffix comparison requires O(n) time, so the overall runtime of this approach is O(n2logn).

接尾辞配列を構築する最も容易な方法は、効率的な比較ソートアルゴリズムを利用することである。接尾辞は O(nlogn)回の比較が必要とされるが、比較ソートアルゴリズムによっては、接尾辞の比較は O(n)回となる。従って全体的な比較回数は O(n2logn)回となる。

もう、訳がどうのこうのいう前に日本語としても意味が分からないし。比較は O(n) 回なのかそれとも O(n log n) 回なのか、それとも O(n^2 log n) 回なのかと。いろいろ無理矢理補完しすぎてもう無茶苦茶。
「O(n) time」 は 「O(n) 回」じゃなくて「O(n) 時間」。回数なら times にならないとおかしい。正しくは「O(n log n)回の比較が必要とされるが、接尾辞の比較は O(n) の時間がかかるので、全体的な実行時間は O(n^2 log n) となる」。
もういっちょ。

To avoid redoing comparisons, extra data structures giving information about the longest common prefixes (LCPs) of suffixes are constructed, giving O(m + logn) search time.

比較をやり直すことの無いように、最も長い文字列である「接頭辞」(LCP)に関する情報を与える余分なデータ構造は、O(m + logn)回の検索をかけて構築される。

おい。これじゃまるで「接尾辞」=「LCP」じゃねーか。そしてやっぱり日本語がおかしい。LCP つうのは Longest Common Prefix のことで、接尾辞間の最長共通接頭辞(の長さ)のこと。LCP の構築には検索は必要ないし、O(n) で計算できる。正しくは「接尾辞間の最長共通接頭辞を計算しておけば、比較のやり直しを避けることができ O(m + log n)の計算時間で済む」だろう。ちなみにこれには Range Minimum Query*1 というのを併用する。
つうか、Applications の前半省略されまくりだな。なぜ、一番重要な部分を省く。
と、こんなことを書いている暇があったら、お前が直せと突っ込まれそうなので(というか自分なら突っ込む)、とりあえず Wikipedia のアカウントを作っておいた。あとで直す。

追記

編集はじめたけど、これもすごいな。

元の文字列が利用可能な場合、それぞれの接尾辞が0起点の添字を付与されたか、それとも1起点によるものかは容易に判断できる。

原文はこれ。

If the original string is available, each suffix can be completely specified by the zero-based or one-based index of its first character.

あはは。

*1:任意区間内の最小値をO(1)で求める

アライドアーキテクツの清水さん

なんか、一部でネタ化されつつあるようだけど、空気を読まずにマジレスすると、ここにコメントを残していったときの id が yushimizu なので、「しみず」と読むんじゃないかな。
そして、例のコメントが SPAM だとか bot っぽいというのはもっともだけど、アライドアーキテクツ株式会社に清水(しみず)さんは実在するっぽいので、あまりネタにするのもいかがなものかな、と思う。

ポインタと参照の違い

メンバ変数のメモリレイアウトは規格で決められていないはずなので、例としては微妙だなぁ。
それはそれとして、思いつく限り、ポインタと参照の違いを並べてみる。

  • 参照には NULL pointer に相当するものはない
    • でも実はやってやれないことはないらしい(コメント欄参照)
  • 参照の変数はは宣言と同時に明示的に初期化しなければならない
  • ポインターは参照先を変更できるが、参照は変更できない。