在列表推导表达式错误中列出remove()方法

时间:2015-10-16 20:36:51

标签: python list-comprehension

我正在创建一个经典的“set”类来练习,我想要做的第一件事是删除所有重复项。我知道我可以使用字典键轻松完成,但我想尝试提高列表理解能力。这两个函数应该做同样的事情,但第二个函数不起作用。为什么呢?

for element in elements:
            if elements.count(element) > 1:
                elements.remove(element)
        print(elements)

第二个:

self.elements = [elements.remove(element) for element in elements
                 if elements.count(element) > 1]

2 个答案:

答案 0 :(得分:4)

不要迭代并从同一个列表中删除,如果你的对象是可清洗的,你还应该使用Counter dict来计算每个元素的出现次数:

from collections import Counter
cn = Counter(elements)
# elements[:] changes original list
elements[:] = (ele for ele in elements if ch[ele] < 2)

在您的第二个代码中,因为list.remove是一个就地操作,只需None's if elements.count(element) > 1 True即可将In [20]: l = [2,3,1,4,1,5] In [21]: l = [l.remove(i) if i > 1 else i for i in l] In [22]: l Out[22]: [None, 1, None, None] 添加到您的列表中什么也不做,所以两个代码示例完全不同。

第一个代码,如果它确实有效只是偶然。当你从列表中删除一个指针指向的元素时,可能会改变,所以你最终会从列表中删除错误的元素。

您的第二个代码正在做什么以及为什么您的第一个代码是错误的方法的示例:

1

因为您更改了指针值,所以最终删除了第二个collections.OrderedDict并添加了一些None,因为像所有在其中运行的函数一样或者在python中没有指定返回值,它们默认返回None。

如果您真的想要获得所有元素的唯一集合,而不仅仅保留您的代码似乎正在尝试的独特元素并维护顺序,from collections import OrderedDict elements[:] = collections.OrderedDict.fromkeys(elements) dict将执行您所需的操作:

public class ResponseWrapperTest {
  @Mocked
  UriInfo uri;

  @Mocked  // I've also tried @Injectable
  MultivaluedMap<String,String> queryParams;

  @Test
  public void ResponseWrapper() throws JSONException {
      final List<String> arSkip = new ArrayList<String>(Arrays.asList("0"));        

      new Expectations() {{
          uri.getQueryParameters();     result = queryParams;
          queryParams.get("skip");      result = arSkip;    
      }};

      ResponseWrapper wrapper = new ResponseWrapper(uri);
}

答案 1 :(得分:1)

您的代码存在两个问题。第一个问题是您明确询问的问题:列表推导版本将为None分配一大堆self.elements值。 None值只是您调用list.remove时的返回值。它会修改列表,并且没有任何有用的东西可以返回(因此返回None)。

理解[element for element in elements if elements.count(element) == 1 or elements.remove(element)]与其他代码的工作方式相同(因为None是假的,or短路),但它仍会遇到第二个问题。 (这也是一个丑陋的黑客攻击:由于elements修改了remove,因此理解创建的新列表与elements具有相同的内容,并且&# 39;令人困惑。写难以理解的代码通常不是一个好主意。)

第二个问题是在迭代时修改列表可能会导致问题。列表迭代器按索引工作。迭代器产生的第一个项目来自索引0,第二个项目来自索引1,依此类推。如果您通过在列表中提前删除项目来修改列表,则会转移所有后续项目的索引。

因此,假设您在迭代器向您显示之后删除了第一个项目(从索引0开始)。该列表将向后移动所有后来的值,但迭代器不会知道这一点。它仍将在索引1处产生项目,即使它曾经是索引2处的项目(在列表被修改之前)。迭代将跳过最初位于索引1的项目(在删除上一项之后的索引0处)。

以下是此问题的一个简单示例,其中不会打印值2,5和8:

L = list(range(10)) # [0,1,2,3,4,5,6,7,8,9]
for x in L:
    print(x)
    if x % 3 == 1: # true for 1,4, and 7
        L.remove(x)

在示例中,删除值的逻辑非常简单,我们永远不会跳过我们通常要删除的值(因此L在结尾处具有期望值[0,2,3,5,6,8,9]),其他代码可能效果不佳。

避免此问题的一种方法是迭代列表的副本,同时修改原始文件。在这种情况下,我们还需要count原文,而不是副本:

for element in elements[:]: # copy list with a slice here!
    if elements.count(element) > 1:
        elements.remove(element)  # modify the original list

这样效率相当低,因为从列表中删除项目(在除结尾之外的位置)需要花时间将所有后面的值移到一个位置。计数也很慢,因为您需要迭代每个项目的整个列表。跟踪您目前为止看到的独特商品效率要高得多,并在以后看到重复商品时跳过重复商品:

seen = set()
results = []
for element in elements:
   if element not in seen:
       seen.add(element)
       results.append(element)

你甚至可以构建一个有点笨拙的列表理解(带有副作用)的代码:

seen = set()
results = [element for element in elements
           if not (element in seen or seen.add(element))]

更好的方法通常是将重复数据删除逻辑捆绑到生成器函数中(如itertools文档中的unique_everseen recipe),然后使用list(dedupe(elements))调用它。