количество вхождений списка слов в строку с O (n)

Я уже видел этот ответ на аналогичный вопрос: https://stackoverflow.com/a/44311921/5881884

Где алгоритм ahocorasick используется, чтобы показать, существует ли каждое слово в списке в строке или нет с O (n). Но я хочу получить частоту каждого слова в списке в строке.

Например, если

my_string = "some text yes text text some"
my_list = ["some", "text", "yes", "not"]

Я хотел бы получить результат:

[2, 3, 1, 0]

Я не нашел точного примера для этого в документации, есть идеи, как это сделать ?

Другие решения O (n), кроме использования ahocorasick, также будут оценены.


person DevB2F    schedule 31.07.2018    source источник


Ответы (4)


Реализация:

Вот частотомер Aho-Corasick:

import ahocorasick

def ac_frequency(needles, haystack):
    frequencies = [0] * len(needles)
    # Make a searcher
    searcher = ahocorasick.Automaton()
    for i, needle in enumerate(needles):
        searcher.add_word(needle, i)
    searcher.make_automaton()
    # Add up all frequencies
    for _, i in searcher.iter(haystack):
        frequencies[i] += 1
    return frequencies

(В вашем примере вы должны вызвать ac_frequency(my_list, my_string), чтобы получить список счетчиков)

Для средних и больших входных данных это будет значительно быстрее, чем другие методы.

Примечания:

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

Если вы хотите найти только полные слова, вы можете вызвать searcher.add_word с версиями исходной строки, дополненными пробелами и знаками препинания:

    ...
    padding_start = [" ", "\n", "\t"]
    padding_end = [" ", ".", ";", ",", "-", "–", "—", "?", "!", "\n"]
    for i, needle in enumerate(needles):
        for s, e in [(s,e) for s in padding_start for e in padding_end]:
            searcher.add_word(s + needle + e, i)
    searcher.make_automaton()
    # Add up all frequencies
    for _, i in searcher.iter(" " + haystack + " "):
    ...
person Ollin Boer Bohan    schedule 31.07.2018
comment
Работает почти идеально, но предложение в примечаниях не находит слов в начале предложения. Если я добавлю: searcher.add_word(needle +, i), он будет считать один и тот же экземпляр дважды. Разве нельзя использовать некоторое регулярное выражение, чтобы убедиться, что оно находит только точное слово? - person DevB2F; 31.07.2018
comment
Я обновил версию в примечаниях, чтобы она стала более полным решением для этого варианта использования. Он должен ловить слова в начале/конце строки (путем заполнения стога сена) и слова сразу после/перед разрывами строк. - person Ollin Boer Bohan; 31.07.2018

Counter в модуле collections может быть вам полезен:

from collections import Counter

my_string = "some text yes text text some"
my_list = ["some", "text", "yes", "not"]

counter = Counter(my_string.split(' '))
[counter.get(item, 0) for item in my_list]

# out: [2, 3, 1, 0]
person dmmfll    schedule 31.07.2018
comment
в чем будет сложность? - person DevB2F; 31.07.2018
comment
Мне было интересно то же самое. Я провожу некоторое расследование, потому что я не компетентен, чтобы сказать. Счетчик оптимизирован. См. это: stackoverflow.com/a/27802189/1913726 - person dmmfll; 01.08.2018
comment
Я сделал несколько тестов %%timeit и построил результаты для разделения строк. Разделение строки - это O (n) в соответствии с этими результатами. Я предполагаю, что поиск экземпляра счетчика будет O (1). - person dmmfll; 01.08.2018
comment
Если ваша строка на самом деле представляет собой просто список элементов (например, в этом случае мы ищем полное совпадение слов, что делает строку списком слов), это лучший подход. - person justhalf; 19.04.2021

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

[my_string.split().count(i) for i in my_list]
[2, 3, 1, 0]
person Onyambu    schedule 31.07.2018
comment
На самом деле это стоит O(n*m), потому что сам метод count() стоит O(n), и вы делаете это для каждого элемента в my_list, который стоит O(m). - person blhsing; 31.07.2018

Вы можете использовать словарь для подсчета вхождений интересующих вас слов:

counts = dict.fromkeys(my_list, 0) # initialize the counting dict with all counts at zero

for word in my_string.split():
    if word in counts:     # this test filters out any unwanted words
        counts[word] += 1  # increment the count

Словарь counts будет содержать количество каждого слова. Если вам действительно нужен список подсчетов в том же порядке, что и исходный список ключевых слов (и dict не подойдет), вы можете добавить последний шаг после завершения цикла:

results = [counts[word] for word in my_list]
person Blckknght    schedule 31.07.2018