примерный RegEx в python с TRE: странное поведение юникода - PullRequest
5 голосов
/ 04 августа 2011

Я пытаюсь использовать библиотеку 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 ]

6 голосов
/ 24 сентября 2011

Вы можете использовать новую библиотеку 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
2 голосов
/ 05 мая 2012

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

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

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

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

0 голосов
/ 24 сентября 2011

Ссылка, которую вы дали, - на статью блога, в которой содержится ссылка на другую статью блога о самом последнем выпуске, в которой есть много ворчливых комментариев, в том числе один, в котором говорится, что пакет не работает с «нелатинскими» (какими бы значит) кодировки. Что заставляет вас верить, что 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 and y` в приведенном выше списке.

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

...