我有两个具有相同数量元素的Python列表。第一个列表的元素是唯一的,第二个列表中的元素 - 不一定如此。例如
list1 = ['e1', 'e2', 'e3', 'e4', 'e5', 'e6', 'e7']
list2 = ['h1', 'h2', 'h1', 'h3', 'h1', 'h2', 'h4']
我想删除第二个列表中的所有“第一个遇到的”元素及其第一个列表中的相应元素。基本上,这意味着删除所有唯一元素和重复项的第一个元素。通过上面的例子,正确的结果应该是
>>>list1
['e3', 'e5', 'e6']
>>>list2
['h1', 'h1', 'h2']
也就是说,元素'e1'被删除了,因为第一次遇到了相应的'h1','e2'被删除了,因为第一次看到'h2','e3'因为'h1'而被删除“已经看到'e4'被删除了,因为'h3'第一次出现了'e5'因为'h1'已经被看到了'e6'因为'h2'已经被看到了'e7'而被删除了'e7' '因为第一次看到'h4'而被删除了。
解决这个问题的有效方法是什么?列表可能包含数千个元素,因此如果可能的话,我宁愿不复制它们或运行多个循环。
答案 0 :(得分:10)
只需使用set
对象查找是否已经看到当前值,如此
>>> list1 = ['e1', 'e2', 'e3', 'e4', 'e5', 'e6', 'e7']
>>> list2 = ['h1', 'h2', 'h1', 'h3', 'h1', 'h2', 'h4']
>>>
>>> def filterer(l1, l2):
... r1 = []
... r2 = []
... seen = set()
... for e1, e2 in zip(l1, l2):
... if e2 not in seen:
... seen.add(e2)
... else:
... r1.append(e1)
... r2.append(e2)
... return r1, r2
...
>>> list1, list2 = filterer(list1, list2)
>>> list1
['e3', 'e5', 'e6']
>>> list2
['h1', 'h1', 'h2']
如果您要逐个使用元素,如果输入列表非常大,那么我建议制作一个像这样的生成器
>>> def filterer(l1, l2):
... seen = set()
... for e1, e2 in zip(l1, l2):
... if e2 not in seen:
... seen.add(e2)
... else:
... yield e1, e2
...
>>> list(filterer(list1, list2))
[('e3', 'h1'), ('e5', 'h1'), ('e6', 'h2')]
>>>
>>> zip(*filterer(list1, list2))
[('e3', 'e5', 'e6'), ('h1', 'h1', 'h2')]
答案 1 :(得分:8)
我可能会在这里打码,但我觉得这很有趣:
list1_new = [x for i, x in enumerate(list1) if list2[i] in list2[:i]]
print(list1_new)
# prints ['e3', 'e5', 'e6']
如果您不熟悉列表推导,此处会发生以下情况(从最后阅读):
i
的元素list2
是否存在于包含所有先前元素list2
的{{1}}切片中。list2[:i]
(list1
)捕获相应的元素,并将其存储在我创建的新列表中x
答案 2 :(得分:7)
一种有效的方法是使用set
,其中包含所有已见过的密钥。 set
将保证您O(1)
的平均查找次数。
所以这样的事情应该有效:
s = set()
result1 = []
result2 = []
for x, y in zip(list1, list2):
if y in s:
result1.append(x)
result2.append(y)
else:
s.add(y)
注意,这将创建一个新列表。但是,不应该是一个大问题,因为Python实际上并没有复制字符串,而只是创建一个指向原始字符串的指针。
答案 3 :(得分:4)
使用集合来跟踪您已遇到的值:
seen= set()
index= 0
while index < len(list1):
i1, i2= list1[index], list2[index]
if i2 in seen:
index+= 1
else:
seen.add(i2)
del list1[index]
del list2[index]
答案 4 :(得分:4)
来自评论:
我希望避免这种情况,并在适当的位置编辑列表
我真的不建议这样做,除非你的代码实际上已经没有内存(或者你有理由期望它会),但它肯定是可能的:
seen = set()
toidx = 0
for first, second in itertools.izip(list1, list2):
if second in seen:
list1[toidx] = first
list2[toidx] = second
toidx += 1
else:
seen.add(second)
del seen
del list1[toidx:]
del list2[toidx:]
C ++的粉丝会将此识别为擦除删除习惯用法。
del
可能会复制你要保留的列表部分,但至少它会一次一个地执行它们,而不是需要同时将所有五个集合放在内存中(两个输入列表,两个输出列表,以及集合seen
)。
如果没有可能的副本,就无法截断列表,因此您可以将列表保留为完整大小,但要记住可以使用多少个值。在这种情况下,您可能应该将末尾的不可用值设置为None
,以便可以释放任何未从其他地方引用的已删除元素。
列表可能包含数千个元素
如果你使用的是真正的电脑,而不是一些蚀刻在针头上的微型机器,那么数以千计的元素就不算什么了。列表每个元素大约需要8个字节。将同一对象存储在多个列表中不需要该对象的副本。因此,为输出使用两个额外的列表将占用每对输入16字节的量级:10k元素为160kB。对于扩展,我正在编写此答案的浏览器目前正在使用1GB的RAM。在运行时运行SO比在适当位置修改列表要大得多的内存优化; - )
减少内存使用量可以帮助缓存性能。如果你有数亿个元素,那么就地修改可能是你的代码运行或失败之间的差异。
答案 5 :(得分:3)
你试试看:
>>> list1 = ['e1', 'e2', 'e3', 'e4', 'e5', 'e6', 'e7']
>>> list2 = ['h1', 'h2', 'h1', 'h3', 'h1', 'h2', 'h4']
>>> repeat = list(set([x for x in list2 if list2.count(x) > 1]))
>>> print repeat
['h2', 'h1']
>>> l1=[]
>>> l2=[]
>>> for single_data in repeat:
indices = [i for i, x in enumerate(list2) if x == single_data]
del indices[0]
for index in indices:
l1.append(list1[index])
l2.append(list2[index])
>>> print l1
['e6', 'e3', 'e5']
>>> print l2
['h2', 'h1', 'h1']
答案 6 :(得分:2)
这里:
list1 = ['e1', 'e2', 'e3', 'e4', 'e5', 'e6', 'e7']
list2 = ['h1', 'h2', 'h1', 'h3', 'h1', 'h2', 'h4']
seen = []
output = []
for index in range(len(list1)):
if list2[index] not in seen:
seen.append(list2[index])
else:
output.append(list1[index])
print output