检查列表中的元素是否存在于另一个列表的元素中
我在处理列表的时候遇到了一些问题。基本上,我有一个列表:
a=["Britney spears", "red dog", "\xa2xe3"]
还有另一个列表,看起来像这样:
b = ["cat","dog","red dog is stupid", "good stuff \xa2xe3", "awesome Britney spears"]
我想做的是检查列表 a
中的元素是否属于列表 b
中的某个元素。如果是的话,就把它们从 b
的元素中移除。所以,我希望 b
看起来像这样:
b = ["cat","dog","is stupid","good stuff","awesome"]
在 Python 2.7.x 中,最好的方法是什么呢?
我在想我可以通过循环来检查每个元素,但我不确定这样做是否高效,因为我的列表 b
大约有 5 万个元素。
3 个回答
1
最简单的方法就是直接用列表推导式,只要a
的大小不大,这种方法其实还挺高效的。
b = [i for i in b if i not in a]
2
好吧,我不知道这算不算是现在的python风格,因为在python3中,reduce
被移到了functools
里。有人得给出一个一行代码的例子:
a = ["Britney spears", "red dog", "\xa2xe3"]
b = ["cat","dog","red dog is stupid", "good stuff \xa2xe3", "awesome Britney spears"]
b = [reduce(lambda acc, n: acc.replace(n, ''), a, x).strip() for x in b]
更快的写法是
[reduce(lambda acc, n: acc.replace(n, '') if n in acc else acc, a, x).strip() for x in b]
但是可读性下降了,我觉得就不那么python风格了。
这里有一个处理转移的捕狗者
情况的例子。我借用了mgilson的正则表达式,但我觉得没问题,因为这很简单 :-):
def reducer(acc, n):
if n in acc:
return re.sub('(?:\s+|^)' + re.escape(n) + '(?:\s+|$)', '', acc)
return acc
b = [reduce(reducer, a, x).strip() for x in b]
我把lambda
提取成了一个命名函数,这样更容易阅读。
4
我觉得这里可以用正则表达式:
import re
a=["Britney spears", "red dog", "\xa2xe3"]
regex = re.compile('|'.join(re.escape(x) for x in a))
b=["cat","dog","red dog is stupid", "good stuff \xa2xe3", "awesome Britney spears"]
b = [regex.sub("",x) for x in b ]
print (b) #['cat', 'dog', ' is stupid', 'good stuff ', 'awesome ']
这样,正则表达式引擎可以更好地优化对不同选项的测试。
这里有很多不同的选项,展示了不同的正则表达式是怎么工作的。
import re
a = ["Britney spears", "red dog", "\xa2xe3"]
b = ["cat","dog",
"red dog is stupid",
"good stuff \xa2xe3",
"awesome Britney spears",
"transferred dogcatcher"]
#This version leaves whitespace and will match between words.
regex = re.compile('|'.join(re.escape(x) for x in a))
c = [regex.sub("",x) for x in b ]
print (c) #['cat', 'dog', ' is stupid', 'good stuff ', 'awesome ', 'transfercatcher']
#This version strips whitespace from either end
# of the returned string
regex = re.compile('|'.join(r'\s*{}\s*'.format(re.escape(x)) for x in a))
c = [regex.sub("",x) for x in b ]
print (c) #['cat', 'dog', 'is stupid', 'good stuff', 'awesome', 'transfercatcher']
#This version will only match at word boundaries,
# but you lose the match with \xa2xe3 since it isn't a word
regex = re.compile('|'.join(r'\s*\b{}\b\s*'.format(re.escape(x)) for x in a))
c = [regex.sub("",x) for x in b ]
print (c) #['cat', 'dog', 'is stupid', 'good stuff \xa2xe3', 'awesome', 'transferred dogcatcher']
#This version finally seems to get it right. It matches whitespace (or the start
# of the string) and then the "word" and then more whitespace (or the end of the
# string). It then replaces that match with nothing -- i.e. it removes the match
# from the string.
regex = re.compile('|'.join(r'(?:\s+|^)'+re.escape(x)+r'(?:\s+|$)' for x in a))
c = [regex.sub("",x) for x in b ]
print (c) #['cat', 'dog', 'is stupid', 'good stuff', 'awesome', 'transferred dogcatcher']