Примерный RegEx в Python с TRE: странное поведение Unicode

Я пытаюсь использовать TRE -библиотеку в python для сопоставления ввода с ошибками.
Важно, что он хорошо обрабатывает строки в кодировке utf-8.

пример:
Немецкую столицу зовут Берлин, но из произношения это то же самое, если бы люди написали "Bärlin"

Пока это работает, но если не-ASCII символ находится на первой или второй позиции обнаруженной строки, ни диапазон, ни сама обнаруженная строка не являются правильными.

# -*- coding: utf-8 -*-
import tre

def apro_match(word, list):
    fz = tre.Fuzzyness(maxerr=3)
    pt = tre.compile(word)
    for i in l:
        m = pt.search(i,fz)
        if m:
            print m.groups()[0],' ', m[0]

if __name__ == '__main__':
    string1 = u'Berlín'.encode('utf-8')
    string2 = u'Bärlin'.encode('utf-8')    
    string3 = u'B\xe4rlin'.encode('utf-8')
    string4 = u'Berlän'.encode('utf-8')
    string5 = u'London, Paris, Bärlin'.encode('utf-8')
    string6 = u'äerlin'.encode('utf-8')
    string7 = u'Beälin'.encode('utf-8')

    l = ['Moskau', string1, string2, string3, string4, string5, string6, string7]

    print '\n'*2
    print "apro_match('Berlin', l)"
    print "="*20
    apro_match('Berlin', l)
    print '\n'*2

    print "apro_match('.*Berlin', l)"
    print "="*20
    apro_match('.*Berlin', l)

выход

apro_match('Berlin', l)
====================
(0, 7)   Berlín
(1, 7)   ärlin
(1, 7)   ärlin
(0, 7)   Berlän
(16, 22)   ärlin
(1, 7)   ?erlin
(0, 7)   Beälin



apro_match('.*Berlin', l)
====================
(0, 7)   Berlín
(0, 7)   Bärlin
(0, 7)   Bärlin
(0, 7)   Berlän
(0, 22)   London, Paris, Bärlin
(0, 7)   äerlin
(0, 7)   Beälin

Не то чтобы регулярное выражение '.*Berlin' это работает нормально, а для регулярных выражений 'Berlin'

u'Bärlin'.encode('utf-8')    
u'B\xe4rlin'.encode('utf-8')
u'äerlin'.encode('utf-8')

не работают, пока

u'Berlín'.encode('utf-8')
u'Berlän'.encode('utf-8')
u'London, Paris, Bärlin'.encode('utf-8')
u'Beälin'.encode('utf-8')

работать как положено.

Что-то не так с кодировкой? Ты знаешь какой-нибудь трюк?

3 ответа

Решение

Вы могли бы использовать новый regex библиотека, она поддерживает Unicode 6.0 и нечеткое сопоставление:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from itertools import ifilter, imap
import regex as re

def apro_match(word_re, lines, fuzzy='e<=1'):
    search = re.compile(ur'('+word_re+'){'+fuzzy+'}').search
    for m in ifilter(None, imap(search, lines)):
        print m.span(), m[0]

def main():
    lst = u'Moskau Berlín Bärlin B\xe4rlin Berlän'.split()
    lst += [u'London, Paris, Bärlin']
    lst += u'äerlin Beälin'.split()
    print
    print "apro_match('Berlin', lst)"
    print "="*25
    apro_match('Berlin', lst)
    print 
    print "apro_match('.*Berlin', lst)"
    print "="*27
    apro_match('.*Berlin', lst)

if __name__ == '__main__':
    main()

'e<=1' означает, что допускается не более одной ошибки любого рода. Есть три типа ошибок:

  • Вставка, обозначенная "i"
  • Удаление, обозначенное "d"
  • Подстановка, обозначенная "s"

Выход

apro_match('Berlin', lst)
=========================
(0, 6) Berlín
(0, 6) Bärlin
(0, 6) Bärlin
(0, 6) Berlän
(15, 21) Bärlin
(0, 6) äerlin
(0, 6) Beälin

apro_match('.*Berlin', lst)
===========================
(0, 6) Berlín
(0, 6) Bärlin
(0, 6) Bärlin
(0, 6) Berlän
(0, 21) London, Paris, Bärlin
(0, 6) äerlin
(0, 6) Beälin

Внутренне TRE работает на уровне байтов и возвращает позиции байтов. У меня была такая же проблема некоторое время назад - уловки нет!

Я изменил привязки Python, добавил функцию utf8 и функцию, которая строит карту от позиции байта до позиции символа, и небольшую оболочку. Ваш тестовый пример работает, как и ожидалось, при использовании этой оболочки. Я не выпустил модификации, это был скорее быстрый взлом при тестировании TRE - если хотите, просто дайте мне знать.

AFAIK TRE давно не обновлялся, и в текущем выпуске (0.8.0) все еще есть нефиксированные ошибки, связанные с сопоставлением с шаблоном в конце строки (например, поиск "2004 " с использованием шаблона "2004$" дает стоимость 2, а ожидаемая стоимость 1).

Как уже отмечали другие, для Python новый модуль регулярных выражений кажется довольно интересным!

Ссылка, которую вы дали, - на статью блога, в которой содержится ссылка на другую статью блога о самом последнем выпуске, в которой есть много ворчливых комментариев, в том числе один, в котором говорится, что пакет не работает с "нелатинскими" (что бы это ни значило) кодировками, Что заставляет вас верить, что TRE работает с текстом в кодировке UTF-8 (работая на уровне символов, а не на уровне байтов)?

Вы не сообщаете нам, сколько ошибок (вставка, удаление, замена) принимаются как нечеткое совпадение. Вы не говорите нам, если он использует char процедуры или wchar Подпрограммы. Вы действительно ожидаете, что потенциальные ответчики загрузят пакет и прочитают код интерфейса Python?

Можно было бы ожидать, что если бы были доступны процедуры wchar C++, интерфейс Python включал бы привязки, которые делали Python unicode <-> Python str (закодированный в UTF-16LE) <-> C++ wchar - не так ли?

Учитывая, что "рабочие" совпадения для 6-символьных тестовых случаев возвращаются с (0, 7), и один неработающий случай (строка 6) разделяет двухбайтовый символ (печатается как ? поскольку ответ не является допустимым UTF-8), похоже, что он работает в режиме, независимом от кодирования байтов (символов) - вообще не очень хорошая идея.

Обратите внимание, что если все остальное терпит неудачу и все ваши входные данные на немецком языке, вы можете попробовать использовать кодировку latin1 или cp1252 с байтовым режимом.

Некоторые дальнейшие замечания:

Ваша строка3 избыточна - она ​​такая же, как строка2.

Ваше утверждение о том, что string5 "работает", кажется несовместимым с вашими утверждениями о том, что string2 и string3 "работают".

Ваше тестовое покрытие редкое; для этого нужно несколько несоответствующих случаев, которые намного ближе к сопоставлению, чем "Moskau"!

Вы должны убедиться, что он сначала "работает" с данными только для ASCII; Вот несколько тестов:

Berlxn Berlxyn
Bxrlin Bxyrlin
xerlin xyerlin
Bexlin Bexylin
xBerlin xyBerlin
Bxerlin Bxyerlin
Berlinx Berlinxy
erlin Brlin Berli

Затем запустите его с не-ASCII символами вместо каждого из x andy` в приведенном выше списке.

Использование шаблона типа ".*Berlin" не очень полезно для диагностических целей, особенно если у вас нет значимых тестовых примеров "не должно совпадать".

Другие вопросы по тегам