从列表中删除第一个遇到的元素

时间:2016-08-17 08:18:46

标签: python list

我有两个具有相同数量元素的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'而被删除了。

解决这个问题的有效方法是什么?列表可能包含数千个元素,因此如果可能的话,我宁愿不复制它们或运行多个循环。

7 个答案:

答案 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