Python で Suffix Array の構築

import codecs, re

def most_long_match(a, b, array, i=1):
   while cmp(array[a:a+i], array[b:b+i]) == 0:
      i += 1
      if len(array) - a == i:
         break
   return cmp(array[a:a+i], array[b:b+i])

mat = re.compile('<.+?>')
 
fp = codecs.open('04.txt', 'r', 'utf8')
body = ''.join( line for line in fp if (not mat.match(line)) )
fp.close()

suffix = sorted(range(len(body)), cmp=lambda x,y:most_long_match(x, y, body))

なんで slice を伸ばし続ける。だんだんと比較のコストがでかくなるじゃないか。さらに 境界の判定は a + i だけ(しかも判定式が直感的でない)で、b + i の方にはないし。つうかもうちょいモジュール化を意識して書いたほうがいいと思うなぁ。
で、適当に書いてみた。

import sys
from itertools import izip

class SuffixComparator(object):
    __slots__ = 'text length'.split(' ')
    
    def __init__(self, text):
        self.text = text
        self.length = len(text)

    def __call__(self, a, b):
        for i, j in izip(xrange(a, self.length), xrange(b, self.length)):
            c = cmp(self.text[i], self.text[j])
            if c: return c
        return cmp(j, i)

def build_suffix_array(text, indices = None):
    if indices is None:
        indices = range(len(text))

    indices.sort(SuffixComparator(text))
    return indices

def read_file(filename):
    fp = file(filename)
    try:
        return fp.read()
    finally:
        fp.close()

def suffix(text, index, max_length):
    last = text.find('\n', index, index + max_length)
    if last != -1:
        return text[index:last]
    else:
        return text[index:index + max_length]

def main(args, options):
    if args:
        text = ''.join(read_file(f) for f in args)
    else:
        text = sys.stdin.read()

    if options.encoding:
        text = text.decode(options.encoding)

    suffix_array = build_suffix_array(text)
    for i in suffix_array:
        sys.stdout.write('%d\t%s\n' % (i, suffix(text, i, 10).encode('utf-8')))

if __name__ == '__main__':
    from optparse import OptionParser
    
    parser = OptionParser()
    parser.add_option('-e', '--encoding', dest='encoding',
                      help='input encoding', metavar='encoding')
    (options, args) = parser.parse_args()

    main(args, options)

微妙に仕様が違うあたりはご愛嬌。
ちなみに Pentium M 2GHz の Windows 上 で 880 Kbytes のテキストに対して 30秒 ぐらいかかる。psyco と併用した場合は 16 秒ぐらいになるけど、どちらにしても遅い。
ナイーブなアルゴリズムってのもあるけど、こういう処理はやっぱり C/C++ で書いて SWIGバインディング作るってのが定番だろうなぁ。