Производительность Python: удалить элемент из списка

У меня есть список длиной: 370000. В этом списке у меня есть такие элементы, как: "a", "y", "Y", "q", "Q", "p", "P",, что означает, что это список слов, но время от времени я получаю эти отдельные символы.

Я хочу удалить эти символы из списка, я новичок в python, но первое, что пришло мне в голову, это сделать что-то вроде:

for word in words:
    if word== 'm' or  word== 'y' or word== 'Y' or word== 'p' or word== 'Q' or word== 'q' or word== 'a' or word== 'uh':
        words.remove(word)

В списке из 370 000 элементов этот метод занимает уйму времени. Серьезно, много.

У кого-нибудь есть еще одна потрясающая идея о том, как повысить производительность?

Заранее спасибо.


person NachoMiguel    schedule 02.10.2015    source источник
comment
Это не просто проблемы с производительностью; вы тоже будете пропускать слова. См. Цикл забывает удалить некоторые элементы   -  person Martijn Pieters    schedule 03.10.2015
comment
откуда вы берете данные?   -  person Padraic Cunningham    schedule 03.10.2015
comment
Случайные тексты. Просто для удовольствия   -  person NachoMiguel    schedule 06.10.2015


Ответы (5)


Пробовал некоторые bogo-бенчмарки в IPython.

import random
# Don't know how to generate your words, use integers as substitute.
words = [random.randint(0, 25) for i in xrange(370000)]
badlist = range(7)
badtuple = tuple(badlist)
badset = set(badlist)
# List comprehension
%timeit [w for w in words if w not in badlist]
10 loops, best of 3: 59.2 ms per loop
%timeit [w for w in words if w not in badtuple]
10 loops, best of 3: 64.7 ms per loop
%timeit [w for w in words if w not in badset]
10 loops, best of 3: 30.3 ms per loop
# Filter
%timeit filter(lambda w: w not in badlist, words)
10 loops, best of 3: 85.6 ms per loop
%timeit filter(lambda w: w not in badtuple, words)
10 loops, best of 3: 92.1 ms per loop
%timeit filter(lambda w: w not in badset, words)
10 loops, best of 3: 50.8 ms per loop

Вывод: Возможно, лучше всего использовать понимание списка с not in <set> в качестве условия фильтра.

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


Некоторая идея о том, почему понимание списка + «не в наборе», вероятно, является оптимальным.

  1. filter против понимания списка: filter на самом деле вызывает callable-вызов, а callable-вызов в Python имеет свои собственные накладные расходы (создание фрейма стека и т. д.). Кроме того, filter пытается быть умным и возвращает правильный тип, что увеличивает накладные расходы. (на самом деле это ничтожно мало). Вместо этого проверка условия включения в список (предложение if ...) имеет меньшие накладные расходы, чем вызов. Это просто вычисление выражений без всех наворотов стека вызовов Python.
  2. Тест на членство в наборе - O (1) в среднем и O (n) в худшем случае, но членство в списке / кортеже всегда O (n).
person Cong Ma    schedule 02.10.2015
comment
импортировать строку и [random.choice(string.ascii_uppercase + string.ascii_lowercase) для _ в диапазоне (37000)] - person midori; 03.10.2015
comment
@BigOldTree Я имею в виду, я понятия не имею, действительно ли так выглядят данные ОП, поэтому я отказался от догадок и просто использовал некоторые цифры, что быстро и грязно;) - person Cong Ma; 03.10.2015
comment
я понимаю, я получил разные результаты с символами, поэтому самый быстрый - это лямбда с кортежем - person midori; 03.10.2015
comment
помимо всего прочего, это лучший ответ, чтобы указать OP в правильном направлении (т.е. проверить разные способы) - person Paul Evans; 03.10.2015
comment
Невозможно воспроизвести результат @BigOldTree , используя случайные односимвольные строки с размером тестового набора 370000 или 37000. Но в любом случае эталонный тест в основном фальшивый. - person Cong Ma; 03.10.2015
comment
я готовил тот же ответ, но для символов, Конг Ма опубликовал это первым, поэтому я просто добавил комментарий - person midori; 03.10.2015
comment
что означает Также фильтр пытается быть умным и возвращает правильный тип? - person Padraic Cunningham; 03.10.2015
comment
@PadraicCunningham, после дальнейших размышлений, здесь не имеет значения, поскольку проверка типов с помощью filter() - это просто постоянные небольшие накладные расходы. Python 3.x устранил это, заставив filter() всегда возвращать итератор. - person Cong Ma; 03.10.2015
comment
@CongMa, python не выполняет проверку типов - person Padraic Cunningham; 03.10.2015
comment
@PadraicCunningham Согласно источнику, это так в реализации filter(), но это просто постоянные накладные расходы. - person Cong Ma; 03.10.2015
comment
На самом деле это не проверка типов, также фильтр часто работает быстрее, чем список композиций для больших данных, поскольку это делается на уровне c, что заставляет код работать медленнее, так это лямбда - person Padraic Cunningham; 03.10.2015
comment
Я полагаю, что filter() должен быть быстрее на str, unicode и, возможно, tuple, потому что возвращаемый объект создается напрямую. OTOH, делать то же самое, что и filterсоставлять str, используя listcomp, за которым следует "".join, было бы расточительно. Лямбда является досадной необходимостью filter, но в данном случае ее можно полностью избежать с помощью listcomp. Когда условие фильтрации требует сложной логики и функция фильтрации необходима даже в listcomp, я ожидаю, что filter в списке будет иметь примерно ту же производительность, что и listcomp. - person Cong Ma; 03.10.2015
comment
stackoverflow.com/a/31640292/2141635 - person Padraic Cunningham; 03.10.2015
comment
@PadraicCunningham Я действительно не вижу разницы в производительности между In [19, 20, and 21]. Во всяком случае, это не противоречит тому, что я сказал, и ортогонально ему. Как видно из источника, оценка filter(None, ...) полностью избегает вызова PyObject_Call, и поэтому в этом случае он может работать быстро. Но бывают случаи, когда лямбду в filter нельзя избежать (в то же время ее можно избежать с помощью синтаксиса listcomp), и это случай OP. - person Cong Ma; 03.10.2015
comment
@CongMa, вы не видите, что использование фильтрации быстрее, чем составление списка? Я также уже говорил, что лямбда — это дорогостоящая операция, разговор о проверке типов с фильтром просто запутывает проблему, накладные расходы связаны с лямбдой. Вы бы лучше объяснили фактическую сложность решений. - person Padraic Cunningham; 03.10.2015
comment
Подход с набором будет линейным, если только вам не повезло и у вас много значений, хеширующих одно и то же значение хеш-функции, все другие решения, не использующие набор, имеют худшую сложность, поэтому двусмысленности нет, для больших n O(n) будет лучше O(k^2)) или O(n*k) - person Padraic Cunningham; 03.10.2015
comment
Эта ветка становится слишком длинной... Согласно @PadraicCunningham, некоторые комментарии вычеркнуты. Кроме того, я не вижу разницы между входными ячейками 19–21 в этом посте. В основном доминирует вызываемый вызов Python. - person Cong Ma; 04.10.2015

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

words = [word for word in words if word not in ["a", "y", "Y", "q", "Q", "p", "P", "uh"]]

понимание списка дает намного лучшую производительность.

Изменить (благодаря результатам Конг Ма):

Похоже, что наилучшая производительность достигается при использовании set в качестве последовательности фильтров, поэтому вам нужно что-то вроде:

words = [word for word in words if word not in set(("a", "y", "Y", "q", "Q", "P", "uh"))]
person Paul Evans    schedule 02.10.2015
comment
Да, должно быть немного быстрее, чем filter(). - person Cong Ma; 03.10.2015
comment
Я предполагаю, что создание набора в списке comp может быть не очень эффективным способом, также words[:] = ... изменит исходный список - person Padraic Cunningham; 03.10.2015

«но время от времени я получаю эти отдельные символы».

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

Я столкнулся с той же проблемой, сначала мое решение использует pypy

Я думаю, что в то время была проблема с pypy (мой код внезапно завершился), поэтому я изменил код с лучшей логикой и использовал обычный python.

person nafsaka    schedule 02.10.2015

Попробуйте конвейер генератора; у этого есть простое приложение. Генераторы имеют хорошую производительность и часто могут привести к меньшему использованию памяти, поскольку конвейеры не создают огромных временных списков (хотя мой окончательный список нарушает этот принцип).

bad_list = ["a", "y", "Y", "q", "Q", "p", "P", "uh"]

# here is the generator "comprehension"
good_word_stream = (word for word in open("lexicon") if word not in bad_list)

# ... and use the output for something
print [len(word) for word in good_word_stream]
person Prune    schedule 03.10.2015

Изменение списка на лету - не лучшая идея, когда у вас достаточно памяти, легко ошибиться, как сказано в комментарии.

Что касается производительности, list.remove - это операция O (n), поэтому ваш код - O (N ^ 2).

Понимание списков происходит намного быстрее, так как занимает больше места — создайте новый список/или генератор в Python 3, используйте небольшой черный список для фильтрации окончательных результатов. Хотя я не уверен, будет ли он создавать ["a", "y", "Y", "q", "Q", "p", "P", "uh"] каждый раз, в удаленном ответе Конг Ма упоминалось о создании этого небольшого набора (да, набор, a in set() - это операция O (1)!), Что может быть полезно для производительности.

И понимание списка примерно на 25% медленнее, чем map или list(map(something)) в моем предыдущем тесте, я не могу доказать это прямо сейчас, но вы, возможно, захотите пройти тест.

Pypy/Cython был бы окончательным решением, если бы все, что вы можете сделать в Python, было сделано, а производительность все еще не соответствовала производственным требованиям.

person SnoopyGuo    schedule 03.10.2015
comment
Я временно удалил свой ответ, чтобы предотвратить редактирование. Я добавил несколько тестов (хотя и фиктивных, поскольку я использовал некоторые числа вместо строк, поскольку у нас нет данных ОП). - person Cong Ma; 03.10.2015
comment
@CongMa, не могли бы вы помочь мне проверить map на понимание списка? В настоящее время я далеко от настольного компьютера/ноутбука.. - person SnoopyGuo; 04.10.2015