Reputation: 7623
PUNCT_CHARS = { '(', ')', ',', ',', '、', ':', ':', '[', ']', '#'}
words = ['a', '#good', 'student']
for word in words.copy():
for char in PUNCT_CHARS:
if char in word:
words.remove(word)
break
print(words)
['a', 'student']
I want to remove words that contain punctuations. Can the 2nd for loop be replaced with an 'any' or 'all' function to make it more efficient?
Upvotes: 2
Views: 91
Reputation: 192
There are already several solutions, all of which seem to yield the result you want. One that is not mentioned, yet, is:
words = [w for w in words if not [c for c in PUNCT_CHARS if c in w]]
But you probably would like to know which one is fastest, out of all of the ones proposed. I have timed them for comparison.
from timeit import timeit
import re
def f1():
PUNCT_CHARS = { '(', ')', ',', ',', '、', ':', ':', '[', ']', '#'}
words = ['a', '#good', 'student']
for word in words.copy():
for char in PUNCT_CHARS:
if char in word:
words.remove(word)
break
def f2():
PUNCT_CHARS = { '(', ')', ',', ',', '、', ':', ':', '[', ']', '#'}
words = ['a', '#good', 'student']
words = [w for w in words if not [c for c in PUNCT_CHARS if c in w]]
def f3():
PUNCT_CHARS = { '(', ')', ',', ',', '、', ':', ':', '[', ']', '#'}
words = ['a', '#good', 'student']
words = list(filter(lambda word: not (set(word) & PUNCT_CHARS), words))
def f4():
PUNCT_CHARS = { '(', ')', ',', ',', '、', ':', ':', '[', ']', '#'}
words = ['a', '#good', 'student']
words[:] = [w for w in words if PUNCT_CHARS.isdisjoint(w)]
def f5():
PUNCT_CHARS = { '(', ')', ',', ',', '、', ':', ':', '[', ']', '#'}
words = ['a', '#good', 'student']
words = [i for i in words if not any(k in i for k in PUNCT_CHARS)]
def f6():
PUNCT_CHARS = { '(', ')', ',', ',', '、', ':', ':', '[', ']', '#'}
words = ['a', '#good', 'student']
r = re.compile(f"[{re.escape(''.join(PUNCT_CHARS))}]")
words = [w for w in words if not r.search(w)]
print(timeit(f1))
print(timeit(f2))
print(timeit(f3))
print(timeit(f4))
print(timeit(f5))
print(timeit(f6))
Here are the results:
8.882432685000822
14.432570434000809
9.28880862799997
5.890979707000952
140.1832275639972
10.627997105999384
(I re-initialized the arrays in each function in case there were any side-effects on optimization that might skew the performance results in particular cases and to keep it as close as possible to the original code.)
However, for the solution with regular expression, compiling the regular expression just once yields a time that might be even a little faster:
3.643752547002805
Especially for a longer list of words, using a regular expression might turn out to be better. I am unsure. It might also depend on whether your punctuations change or just your list of words. If you need to perform this step just once, on a short list of words, using isdisjoint()
seems best. If you need to perform this operation many times, on a long list of words, with fixed punctuation, probably compile the regular expression once and reuse it. But if you care about performance in the first place, then it seems it's likely the latter.
It seems that the version with isdisjoint()
and regular expression are the fastest out of the ones proposed, with the version using any
trailing far behind. I am using Python3.10 under MacOS (2015 MacBook, with 2.9GHz Intel CPU).
I am not sure how to properly proceed with my answer in such a case. The answers for the code cited probably deserve the votes.
(This is an aside: what you might be intending to do with your code originally was to tokenize a text and then filter out words, as I have encountered code like this many times before. In that case, the fastest approach might be "none of the above". It might be better to filter out words with those punctuation characters in the first place, as you tokenize the text, again probably using a regular expression, so that you won't have to do the second filtering pass at all, which gives you the fastest possible run-time of "zero" for your filtering step.)
Upvotes: 0
Reputation: 36441
You could join your punctuation characters into a regular expression and use that to filter your list.
>>> import re
>>> r = re.compile(f"[{re.escape(''.join(PUNCT_CHARS))}]")
>>> [w for w in words if not r.search(w)]
['a', 'student']
>>>
Upvotes: 1
Reputation: 598
for word in words.copy():
if not any(char in word for char in PUNCT_CHARS):
print(word)
Or you can use list comprehension to make such a list:
words=[i for i in words if not any(k in i for k in PUNCT_CHARS)]
Upvotes: 1
Reputation: 260290
Take advantage of your PUNCT_CHARS
set to check if the sets of characters are disjoint:
out = [w for w in words if PUNCT_CHARS.isdisjoint(w)]
Output: ['a', 'student']
To modify your original object:
words[:] = [w for w in words if PUNCT_CHARS.isdisjoint(w)]
Upvotes: 1