Подсчет появления имени в текстовом файле, чувствительность к дубликатам - PullRequest
0 голосов
/ 01 апреля 2020

У меня есть список имен и я хочу подсчитать вхождения в текстовых файлах.

Я использую простой поиск по регулярному выражению со словарем, чтобы сделать это:

    for k,v in eng_names_dict.items():
        for i in v:
            pattern = re.compile(str(i).strip(' '))
            matches = re.search(pattern, text)
            if matches:
                namesDict[k] += 1
                break
    return

Подвох:

У меня есть смесь названий и имен (с разными форматами имен, как показано в примере ниже), с некоторыми дубликатами между ними.

Для Пример: в моем списке два разных человека - «Доктор Марк» (название + фамилия) и «Марк Смит» (имя + фамилия).

Если текстовый файл содержит строку «Доктор Марк Смит сказал что ... "моя функция помечает счет для обоих людей (а не только для" Марка Смита ").

Есть ли способ обеспечить только один счет на подстроку?

1 Ответ

1 голос
/ 01 апреля 2020

Ах, спасибо, что включили структуру данных. Я думаю, что вам нужно "или" функциональность в регулярных выражениях. Рассмотрим этот пример

regex = r'Mr\. John Smith|John Smith'
re.findall(regex, "I hate Mr. John Smith)

# -> ['Mr. John Smith'] 

Итак, чтобы объяснить, канал в регулярном выражении действует как «или», то есть соответствует либо тому, либо другому, но не обоим, а регулярное выражение жадное будет соответствовать самому длинному шаблону, если между ними есть вложенность.

В приведенном мною примере и "Мистер Джон Смит", и "Джон Смит" были совпадением, но регулярное выражение выбрало совпадение с более длинным. Также обратите внимание, что findall () возвращает список всех совпадений. Итак, применив это к вашему случаю:

for k,v in eng_names_dict.items():

    # Convert list of matches into one regex string
    regex = r'|'.join(v)
    matches = re.findall(regex, text)
    namesDict[k] += len(matches)

EDIT

Хорошо, так что из вашего комментария кажется, что может быть неоднозначность в значениях различных ключей eng_names_dict, тогда как до сих пор Мой ответ касается только двусмысленности значений в одном ключе.

Вот два способа справиться с ситуацией и ограничения каждого из них. С помощью регулярных выражений иногда возникает неоднозначность, которая должна быть решена с помощью жестко закодированных правил.

Сценарий 1: небольшое количество таких неоднозначных случаев.

Если величина перекрытия между значениями мала и поддается управлению , вы могли бы упорядочить свои выражения регулярных выражений в соответствии с предпочтениями и по частям удалить соответствующую фразу в тексте.

Так, например, если у нас есть:

{'Mark Smith': ['Dr. Mark Smith', 'Mark Smith'],
 'Andrew Mark': ['Dr. Mark', 'Andrew Mark']

Примечание. предполагая, что у Марка Смита есть где-то значение «доктор Марк Смит», даже если вы не говорите, что это обязательно так. Потому что, если это не так, то проблема в чем-то совершенно ином (в этом случае будет то, как сопоставить «Марка Смита», а НЕ как «доктора Марка Смита»).

Мы можем четко видим, что одно из значений Эндрю вложено в одно из значений Марка, поэтому мы можем сначала выбрать Марка (согласно некоторому правилу), а затем удалить фразу из текста.

from collections import OrderedDict 

od = OrderedDict()
od['Mark Smith'] = eng_names_dict['Mark_Smith']
od['Andrew Mark'] = eng_names_dict['Andrew Mark']

for k,v in eng_names_dict.items():

    # Convert list of matches into one regex string
    regex = r'|'.join(v)
    matches = re.findall(regex, text)
    for match in set(matches):
        text=re.sub(r'{}'.format(match, '', text)
    namesDict[k] += len(matches)

Недостатком здесь является ручное требование для определения порядка операций при использовании записей eng_name_dicts.

Сценарий 2: количество дел слишком велико


В этом случае мы можем просто продолжать использовать естественное поведение регулярных выражений для выбора самой длинной строки в совпадениях с "или". Немного переделайте исходное решение. Вместо создания небольшого регулярного выражения для каждого ключа eng_names_dict, давайте создадим один действительно большой для каждого возможного значения. Регулярное решение решит для нам, каков правильный порядок.

# Create one list containing all values from dict
match_vals = []
for dict_val in list(eng_names_dict.values()):
    for match_val in dict_val:
        match_vals.extend(match_val)

# Do a match on this full regex
regex = r'|'.join(match_vals)
matches = re.findall(regex, text)

# Loop through every match, and count it if it's in the vals of an entry's key
for match in matches:
    for k, v in eng_names_dict.items():
        # Nested loops will be slow; open to suggestions to improve
        if match in v:
            namesDict[k] + 1
            # Any match is unique to one person; break loop after match found
            break

Преимущество в том, что регулярное выражение будет естественным образом определять наиболее точный порядок, так что вам не нужно это выяснять вручную. Недостатком здесь является то, что он сложен, труден для отладки и может повлиять на отношения между значениями имен, которые вы сами не знаете.

...