2012-01-05 2 views
0

Я пытаюсь выполнить функцию, позволяющую найти количество вхождений (целых) слов (без учета регистра) в текст.Python - Количество слов вхождения

Пример:

>>> text = """Antoine is my name and I like python. 
Oh ! your name is antoine? And you like Python! 
Yes is is true, I like PYTHON 
and his name__ is John O'connor""" 

assert(2 == Occs("Antoine", text)) 
assert(2 == Occs("ANTOINE", text)) 
assert(0 == Occs("antoin", text)) 
assert(1 == Occs("true", text))  
assert(0 == Occs("connor", text)) 
assert(1 == Occs("you like Python", text)) 
assert(1 == Occs("Name", text)) 

Вот основная попытка:

def Occs(word,text): 
    return text.lower().count(word.lower()) 

Это один не работает, потому что это не основано на словах.
Эта функция должна быть быстрой, текст может быть очень большим.

Должен ли я разделить его на массив?
Есть ли простой способ сделать эту функцию?

Редактировать (питон 2.3.4)

+0

Регулярные выражения? http://docs.python.org/howto/regex.html – Li0liQ

+0

Сколько у вас запросов? Если у вас их много, я предлагаю вам разделить текст в нижнем регистре на слова (O (n)), сортировать их и искать в приведенном списке (бинарный поиск + итерация по смежным itmes) –

+0

Почему на небесах вы привязаны к Python 2,3? – jsbueno

ответ

7
from collections import Counter 
import re 

Counter(re.findall(r"\w+", text)) 

или для регистронезависимой версии

Counter(w.lower() for w in re.findall(r"\w+", text)) 

В Python 2.7 <, используйте defaultdict вместо Counter:

freq = defaultdict(int) 
for w in re.findall(r"\w+", text): 
    freq[w.lower()] += 1 
+0

Для версии с жестким диском, почему бы просто не использовать флаг 're.IGNORECASE'? http://docs.python.org/library/re.html#re.IGNORECASE –

+0

@DaveWebb: 'IGNORECASE' будет игнорировать регистр во время сопоставления, но не будет содержать строчные результаты' findall'. –

+0

Вопрос заключается в просьбе подсчета определенного слова, а не для всех слов; Думаю, в этом случае «IGNORECASE» имеет больше смысла. –

0

См. this question.

Одна из реализаций заключается в том, что если ваш файл ориентирован на линию, то чтение его по строке и использование простой split() на каждой строке не будет дорогостоящим. Это, конечно, предполагает, что слова не охватывают разрывы строк, так или иначе (без дефис).

+0

Спасибо, но это не исключительно ориентированное на линию –

2

Вот не-вещий способ - Я предполагаю, что это домашнее задание вопрос все равно ...

def count(word, text): 
    result = 0 
    text = text.lower() 
    word = word.lower() 
    index = text.find(word, 0) 
    while index >= 0: 
     result += 1 
     index = text.find(word, index) 
    return result 

Конечно, для очень больших файлов, это будет медленно в основном за счет text.lower() вызов. Но вы всегда можете придумать нечувствительность к регистру find и исправить это!

Почему я так делаю? Потому что я думаю, что он захватывает то, что вы пытаетесь сделать лучше всего: пройдите text, подсчитывая, сколько раз вы находите word.

Кроме того, эти методы решают некоторые неприятные проблемы с пунктуацией: split оставит их там, и вы не будете соответствовать тогда, не так ли?

+0

Может ли этот матч 'NumberOfOccurencesOfWordInText (« antoin », text)'? Это не должно. В любом случае +1 для более низкой() производительности. –

+1

@ Правильно, мой плохой. Тем не менее, техника будет работать, вам просто нужно проверить совпадения (начало и конец) для границ слов. Нет простого способа сделать это. Вам просто нужно будет сканировать текст. Подумайте о создании специализированного сканера во время выполнения, чтобы почерпнуть текст, проверяя слово. Что-то вроде 'grep'. –

+0

+1 для границ слов, я думаю, что это ключ –

1

Благодарим за помощь.
Вот мое решение:

import re 

starte = "(?<![a-z])((?<!')|(?<=''))" 
ende = "(?![a-z])((?!')|(?=''))" 

def NumberOfOccurencesOfWordInText(word, text): 
    """Returns the nb. of occurences of whole word(s) (case insensitive) in a text""" 
    pattern = (re.match('[a-z]', word, re.I) != None) * starte\ 
       + word\ 
       + (re.match('[a-z]', word[-1], re.I) != None) * ende 
    return len(re.findall(pattern, text, re.IGNORECASE)) 
+0

работает для меня, и пусть слово имеет цитаты и пробелы. Вы нашли какое-либо другое решение? не слишком ли регулярное выражение? – olanod

0

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

def CountOccurencesInText(word,text): 
    """Number of occurences of word (case insensitive) in text""" 

    acceptedChar = ('a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 
       'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '-', ' ') 

    for x in ",!?;_\n«»():\".": 
     if x == "\n" or x == "«" or x == "»" or x == "(" or x == ")" or x == "\"" or x == ":" or x == ".": 
      text = text.replace(x," ") 
     else: 
      text = text.replace(x,"") 

    """this specifically handles the imput I am attaching my c.v. to this e-mail.""" 
    if len(word) == 32: 
     for x in ".": 
      word = word.replace(x," ") 

    punc_Removed_Text = "" 
    text = text.lower() 

    for i in range(len(text)): 
     if text[i] in acceptedChar: 
     punc_Removed_Text = punc_Removed_Text + text[i] 

     """"this specifically handles the imput: Do I have to take that as a 'yes'""" 
     elif text[i] == '\'' and text[i-1] == 's': 
      punc_Removed_Text = punc_Removed_Text + text[i] 

     elif text[i] == '\'' and text[i-1] in acceptedChar and text[i+1] in acceptedChar: 
      punc_Removed_Text = punc_Removed_Text + text[i] 

     elif text[i] == '\'' and text[i-1] == " " and text[i+1] in acceptedChar: 
      punc_Removed_Text = punc_Removed_Text + text[i] 

     elif text[i] == '\'' and text[i-1] in acceptedChar and text[i+1] == " " : 
      punc_Removed_Text = punc_Removed_Text + text[i] 

    frequency = 0 
    splitedText = punc_Removed_Text.split(word.lower()) 

    for y in range(0,len(splitedText)-1,1): 
     element = splitedText[y] 

     if len(element) == 0: 
      if(splitedText[y+1][0] == " "): 
       frequency += 1 

     elif len(element) == 0: 
      if(len(splitedText[y+1][0])==0): 
       frequency += 1 

     elif len(splitedText[y+1]) == 0: 
      if(element[len(element)-1] == " "): 
       frequency += 1 

     elif (element[len(element)-1] == " " and splitedText[y+1][0] == " "): 
      frequency += 1 
    return frequency 

А вот профиль:

128006 function calls in 7.831 seconds 

    Ordered by: standard name 

    ncalls tottime percall cumtime percall filename:lineno(function) 
     1 0.000 0.000 7.831 7.831 :0(exec) 
    32800 0.062 0.000 0.062 0.000 :0(len) 
    11200 0.047 0.000 0.047 0.000 :0(lower) 
     1 0.000 0.000 0.000 0.000 :0(print) 
    72800 0.359 0.000 0.359 0.000 :0(replace) 
     1 0.000 0.000 0.000 0.000 :0(setprofile) 
    5600 0.078 0.000 0.078 0.000 :0(split) 
     1 0.000 0.000 7.831 7.831 <string>:1(<module>) 
     1 0.000 0.000 7.831 7.831 ideone-gg.py:225(doit) 
    5600 7.285 0.001 7.831 0.001 ideone-gg.py:3(CountOccurencesInText) 
     1 0.000 0.000 7.831 7.831 profile:0(doit()) 
     0 0.000    0.000   profile:0(profiler) 
Смежные вопросы