2013-12-23 4 views
8

Я столкнулся с проблемой скорости, просматривая очень большой список. У меня есть файл с большим количеством ошибок и очень странных слов. Я пытаюсь использовать difflib для поиска ближайшего совпадения в файле словаря, который у меня есть 650 000 слов. Этот подход ниже работает очень хорошо, но очень медленный, и мне было интересно, есть ли лучший способ подойти к этой проблеме. Это код:Python ищет большой список скорости

from difflib import SequenceMatcher 
headWordList = [ #This is a list of 650,000 words] 


openFile = open("sentences.txt","r") 

for line in openFile: 
    sentenceList.append[line] 

percentage = 0 
count = 0 

for y in sentenceList: 
     if y not in headwordList: 

     for x in headwordList: 
      m = SequenceMatcher(None, y.lower(), x) 

      if m.ratio() > percentage: 
       percentage = m.ratio() 

       word = x 

     if percentage > 0.86:   
      sentenceList[count] = word 
count=count+1 

Благодарим за помощь, что разработка программного обеспечения даже близко не подходит для моего сильного костюма. Очень признателен.

+2

Я не» t согласен. Он более или менее ищет альтернативные подходы – keyser

+2

Это проблема структур данных. – wim

+0

Вы имели в виду 'm = SequenceMatcher (None, y.lower(), x)' – wim

ответ

4

Две вещи, которые могли бы обеспечить некоторую небольшую помощь:

1) Используйте подход в this SO answer читать через большой файл наиболее эффективно.

2) Изменить код из

for x in headwordList: 
    m = SequenceMatcher(None, y.lower(), 1) 

в

yLower = y.lower() 
for x in headwordList: 
    m = SequenceMatcher(None, yLower, 1) 

Вы преобразования каждое предложение понизить 650000 раз. Нет необходимости в этом.

3

1) Я бы сохранил headwordList как набор, а не список, позволяющий более быстрый доступ, поскольку это хешированная структура данных.

2) У вас есть sentenceList, который определен как список, затем попытайтесь использовать его в качестве словаря с sentenceList[x] = y. Я бы определил другую структуру специально для подсчетов.

3) Вы строите sentenceList, который не нужно делать.

for line in file: 
    if line not in headwordList... 

4) Вы никогда не токенизировать line означает, что вы храните всю строку до символа новой строки в sentenceList и посмотреть, если он находится в словник

3

Вы должны изменить headwordList в set.

Тест word in headwordList будет очень медленным. Он должен выполнять сравнение строк по каждому слову в headwordList, по одному слову за раз. Это займет время, пропорциональное длине списка; если вы удвоите длину списка, вы удвоете время, необходимое для выполнения теста (в среднем).

С set для проведения теста in всегда требуется столько же времени; он не зависит от количества элементов в set. Так что это будет огромная скорость.

Теперь весь этот цикл может быть упрощена:

 for x in headwordList: 
     m = SequenceMatcher(None, y.lower(), x) 

     if m.ratio() > percentage: 
      percentage = m.ratio() 

      word = x 

    if percentage > 0.86:   
     sentenceList[count] = word 

Все это делает найти слово из headwordList, который имеет самый высокий показатель, и сохранить его (но только сохранить его, если отношение составляет более 0,86) , Вот более быстрый способ сделать это. Я собираюсь изменить имя headwordList на headwords, так как я хочу, чтобы вы сделали его set, а не list.

def check_ratio(m): 
    return m.ratio() 

y = y.lower() # do the .lower() call one time 
m, word = max((SequenceMatcher(None, y, word), word) for word in headwords, key=check_ratio) 
percentage = max(percentage, m.ratio()) # remember best ratio 
if m.ratio() > 0.86: 
    setence_list.append(word) 

Это может показаться немного сложным, но это самый быстрый способ сделать это в Python. Мы будем называть встроенную функцию max(), чтобы найти результат SequenceMatcher, который имеет самый высокий коэффициент. Во-первых, мы строим «выражение генератора», которое пробует все слова в headwords, вызывая SequenceMatcher() на каждом. Но когда мы закончили, мы также хотим знать, что это за слово. Таким образом, выражение генератора создает кортежи, где первое значение в кортеже - результат SequenceMatcher, а второе значение - это слово. Функция max() не может знать, что мы заботимся о соотношении, поэтому мы должны сказать это; мы делаем это, создавая функцию, которая проверяет, что мы заботимся, а затем передаем эту функцию как аргумент key=. Теперь max() находит значение с самым высоким коэффициентом для нас.max() потребляет все значения, генерируемые выражением генератора, и возвращает одно значение, которое мы затем распаковываем в переменные m и word.

В Python лучше использовать имена переменных, такие как sentence_list, а не sentenceList. Пожалуйста, ознакомьтесь с этими рекомендациями: http://www.python.org/dev/peps/pep-0008/

Неправильная практика использования инкрементирующей индексной переменной и назначения в индексированные позиции в списке. Скорее, начните с пустого списка и используйте функцию метода .append() для добавления значений.

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

Обратите внимание, что ваш исходный код, похоже, имеет ошибку: как только любое слово имеет процент более 0,86, все слова сохраняются в sentenceList независимо от их отношения. Код, который я написал выше, только сохраняет слова, где собственное соотношение слова было достаточно высоким.

EDIT: Это ответ на вопрос об генераторных выражениях, которые должны быть заключены в скобки.

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

def check_ratio(m): 
    return m.ratio() 

y = y.lower() # do the .lower() call one time 
genexp = ((SequenceMatcher(None, y, word), word) for word in headwords) 
m, word = max(genexp, key=check_ratio) 
percentage = max(percentage, m.ratio()) # remember best ratio 
if m.ratio() > 0.86: 
    setence_list.append(word) 

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

m, word = max(((SequenceMatcher(None, y, word), word) for word in headwords), key=check_ratio) 

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

Но я думаю, что это легче читать, если вы разделите genexp на своей собственной линии.

EDIT: @Peter Wood указал, что в документации предлагается повторное использование SequenceMatcher для скорости. У меня нет времени проверять это, но я думаю, что это правильный способ сделать это.

К счастью, код стал проще! Всегда хороший знак.

EDIT: Я только что проверил код. Этот код работает для меня; посмотрите, работает ли это для вас.

from difflib import SequenceMatcher 

headwords = [ 
# This is a list of 650,000 words 
# Dummy list: 
    "happy", 
    "new", 
    "year", 
] 


def words_from_file(filename): 
    with open(filename, "rt") as f: 
     for line in f: 
      for word in line.split(): 
       yield word 

def _match(matcher, s): 
    matcher.set_seq2(s) 
    return (matcher.ratio(), s) 

ratios = {} 
best_ratio = 0 

matcher = SequenceMatcher() 

for word in words_from_file("sentences.txt"): 
    matcher.set_seq1(word.lower()) 
    if word not in headwords: 
     ratio, word = max(_match(matcher, word.lower()) for word in headwords) 
     best_ratio = max(best_ratio, ratio) # remember best ratio 
     if ratio > 0.86: 
      ratios[word] = ratio 

print(best_ratio) 
print(ratios) 
+0

Steveha, я считаю этот подход интересным, и я пытаюсь его использовать, но я получаю сообщение об ошибке:« generator выражение должно быть заключено в скобки, если не единственный аргумент «... любые идеи? –

+0

Кроме того, [документы предлагают повторно использовать «SequenceMatcher»] (http://docs.python.org/2/library/difflib.html#sequencematcher-objects): «SequenceMatcher вычисляет и кэширует подробную информацию о второй последовательности, поэтому если вы хотите сравнить одну последовательность с множеством последовательностей, используйте set_seq2(), чтобы установить часто используемую последовательность один раз и многократно называть set_seq1(), один раз для каждой из других последовательностей. ' –

0

Это вопрос структуры данных. То, что вы хотите сделать, состоит в том, чтобы превратить ваш список в нечто с более быстрой скоростью поиска элемента, например, бинарное дерево поиска будет работать отлично: сложность времени - это только O (log n), а не O (n) в списке (что сравнивается невероятно быстро) ,

Там есть довольно простое объяснение здесь:

http://interactivepython.org/runestone/static/pythonds/Trees/balanced.html

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

http://interactivepython.org/runestone/static/pythonds/Trees/trees.html

+1

Это не полезный ответ на вопрос Python. Python включает несколько встроенных модулей, которые могут быть с пользой применены к этому вопросу ('set' и' dict'). @English Grad говорит, что он/она не является экспертом в области программного обеспечения, поэтому страница, показывающая, как реализовать структуру данных, не поможет, и даже эксперт-разработчик программного обеспечения будет лучше обслуживаться с помощью встроенного Python, реализация структуры данных дерева. – steveha

Смежные вопросы