4

我有两个清单,

l1 = [1,2,3,4,5,6]
l2 = [3,2]

我想要的是删除 l2 中的列表 l1 的元素,因为我做了这样的事情,

for x in l1:
    if x in l2:
        l1.remove(x)

它给出了类似的输出

[1, 3, 4, 5, 6]

但输出应该像

[1, 4, 5, 6]

任何人都可以阐明这一点。

4

9 回答 9

9

这很容易解释为这样。

考虑您拥有的第一个数组:

| 1 | 2 | 3 | 4 | 5 | 6 |

现在你开始迭代

| 1 | 2 | 3 | 4 | 5 | 6 |
  ^

什么都没有发生,迭代器递增

| 1 | 2 | 3 | 4 | 5 | 6 |
      ^

2 被删除

| 1 | 3 | 4 | 5 | 6 |
      ^

迭代器增量

| 1 | 3 | 4 | 5 | 6 |
          ^

瞧,3 还在那里。

解决方案是遍历向量的副本,例如

for x in l1[:]: <- slice on entire array
    if x in l2:
        l1.remove(x)

或反向迭代:

for x in reversed(l1):
    if x in l2:
        l1.remove(x)

其行为如下:

| 1 | 2 | 3 | 4 | 5 | 6 |
              ^

| 1 | 2 | 3 | 4 | 5 | 6 |
          ^

| 1 | 2 | 4 | 5 | 6 |
          ^

| 1 | 2 | 4 | 5 | 6 |
      ^

| 1 | 4 | 5 | 6 |
      ^

| 1 | 4 | 5 | 6 |
  ^
于 2012-10-11T13:38:52.390 回答
7

为什么不让它更简单一点?l1如果我们只想删除存在于 中的元素,则无需实际迭代l2

for item in l2:
    while item in l1:
        l1.remove(item)

这为您提供了所需的输出......

此外,正如评论者指出的那样,如果我们有可能重复:

l1 = filter(lambda x: x not in l2, l1)

.. 或使用列表推导的许多其他变体。

于 2012-10-11T13:37:02.283 回答
3

您希望外部循环读取:

for x in l1[:]:
   ...

您不能在迭代列表时更改列表并期望得到合理的结果。上面的技巧会复制l1并迭代副本。

请注意,如果输出列表中的顺序无关紧要,并且您的元素是唯一且可散列的,则可以使用集合:

set(l1).difference(l2)

这将为您提供一组作为输出,但您可以轻松地从中构造一个列表:

l1 = list(set(l1).difference(l2))
于 2012-10-11T13:33:03.667 回答
2

正如其他人所说,您无法在循环访问列表时对其进行编辑。这里一个不错的选择是使用列表推导来创建一个新列表。

removals = set(l2)
l1 = [item for item in l1 if item not in removals]

我们做一个集合,因为对集合的成员资格检查比在列表上要快得多。

于 2012-10-11T13:36:50.417 回答
2

如果 l1 中重复的顺序和丢失无关紧要:

list(set(l1) - set(l2))

仅当您需要将结果作为列表时才需要最后一个 list()。您也可以只使用结果集,它也是可迭代的。如果您需要订购它,您当然可以在结果列表上调用 l.sort() 。

于 2013-08-08T18:56:15.297 回答
1

编辑:删除了我原来的答案,因为即使它确实给出了正确的结果,它也是出于非直觉的原因这样做的,而且速度也不是很快......所以我刚刚离开了时间:

import timeit

setup = """l1 = list(range(20)) + list(range(20))
l2 = [2, 3]"""

stmts = {
"mgilson": """for x in l1[:]:
    if x in l2:
        l1.remove(x)""",
"petr": """for item in l2:
    while item in l1:
        l1.remove(item)""",
"Lattyware": """removals = set(l2)
l1 = [item for item in l1 if item not in removals]""",
"millimoose": """for x in l2:
    try: 
        while True: l1.remove(x)
    except ValueError: pass""",
"Latty_mgilson": """removals = set(l2)
l1[:] = (item for item in l1 if item not in removals)""",
"mgilson_set": """l1 = list(set(l1).difference(l2))"""
}        

for idea in stmts:
    print("{0}: {1}".format(idea, timeit.timeit(setup=setup, stmt=stmts[idea])))

结果(Python 3.3.0 64bit,Win7):

mgilson_set: 2.5841989922197333
mgilson: 3.7747968857414813
petr: 1.9669433777815701
Latty_mgilson: 7.262900152285258
millimoose: 3.1890831105541793
Lattyware: 4.573971325181478
于 2012-10-11T13:38:16.053 回答
0

l1您在迭代列表时正在修改列表,这将导致奇怪的行为。(3在迭代期间将被跳过。)

要么迭代一个副本,要么改变你的算法来迭代l2

for x in l2:
    try: 
        while True: l1.remove(x)
    except ValueError: pass

(这应该比if x in l1显式测试更好。)l1不,随着规模的增长,这表现得非常糟糕。

于 2012-10-11T13:38:11.007 回答
0

FWIW 我得到的结果与@Tim Pietzcker 使用我认为更真实的输入数据集并使用更严格(但在其他方面相同)的方法来计时不同人的答案时得到的结果明显不同。

名称和代码片段与 Tim 的相同,只是我添加了名为Lattywarecalled 的变体,Lattyware_rev它确定要保留而不是拒绝哪些元素 - 结果比前者慢。请注意,两个最快的不保留l1.

这是最新的计时码:

import timeit

setup = """
import random
random.seed(42) # initialize to constant to get same test values
l1 = [random.randrange(100) for _ in xrange(100)]
l2 = [random.randrange(100) for _ in xrange(10)]
"""

stmts = {
"Minion91": """
for x in reversed(l1):
    if x in l2:
        l1.remove(x)
""",

"mgilson": """
for x in l1[:]: # correction
    if x in l2:
        l1.remove(x)
""",
"mgilson_set": """
l1 = list(set(l1).difference(l2))
""",

"Lattyware": """
removals = set(l2)
l1 = [item for item in l1 if item not in removals]
""",
"Lattyware_rev": """
keep = set(l1).difference(l2)
l1 = [item for item in l1 if item in keep]
""",
"Latty_mgilson": """
removals = set(l2)
l1[:] = (item for item in l1 if item not in removals)""",

"petr": """
for item in l2:
    while item in l1:
        l1.remove(item)
""",
"petr (handles dups)": """
l1 = filter(lambda x: x not in l2, l1)
""",

"millimoose": """
for x in l2:
    try:
        while True: l1.remove(x)
    except ValueError: pass
""",

"K.-Michael Aye": """
l1 = list(set(l1) - set(l2))
""",

}

N = 10000
R = 3

timings = [(idea,
            min(timeit.repeat(stmts[idea], setup=setup, repeat=R, number=N)),
           ) for idea in stmts]
longest = max(len(t[0]) for t in timings)  # length of longest name
exec(setup)  # get an l1 & l2 just for heading length measurements
print('fastest to slowest timings of ideas:\n' +\
      '  ({:,d} timeit calls, best of {:d} executions)\n'.format(N, R)+\
      '   len(l1): {:,d}, len(l2): {:,d})\n'.format(len(l1), len(l2)))
for i in sorted(timings, key=lambda x: x[1]):  # sort by speed (fastest first)
    print "{:>{width}}:  {}".format(*i, width=longest)

输出:

fastest to slowest timings of ideas:
  (10,000 timeit calls, best of 3 executions)
   len(l1): 100, len(l2): 10)

        mgilson_set:  0.143126456832
     K.-Michael Aye:  0.213544010551
          Lattyware:  0.23666971551
      Lattyware_rev:  0.466918513924
      Latty_mgilson:  0.547516608553
               petr:  0.552547776807
            mgilson:  0.614238139366
           Minion91:  0.728920176815
         millimoose:  0.883061820848
petr (handles dups):  0.984093136969

当然,如果有什么根本性的错误可以解释完全不同的结果,请告诉我。

于 2012-10-11T20:17:41.550 回答
0
l1 = [1, 2, 3, 4, 5, 6]
l2 = [3, 2]
[l1.remove(x) for x in l2]
print l1
[1, 4, 5, 6]
于 2015-02-12T09:17:28.230 回答