【问题标题】:A quick way to return list without a specific element in Python在 Python 中返回没有特定元素的列表的快速方法
【发布时间】:2013-04-01 06:29:25
【问题描述】:

如果我有一个任意顺序的卡片套装列表,如下所示:

suits = ["h", "c", "d", "s"]

我想返回一个没有'c'的列表

noclubs = ["h", "d", "s"]

有没有简单的方法来做到这一点?

【问题讨论】:

  • 有没有办法在不改变原始列表的情况下做到这一点?
  • @AsheKetchum 不幸的是,您最终必须复制该列表。

标签: python


【解决方案1】:
suits = ["h","c", "d", "s"]

noclubs = [x for x in suits if x != "c"]

【讨论】:

  • 我不能说谎...我希望会有一些没有循环的东西。这在 R 等语言中更短更直观。
  • 我会避免使用这种解决方案,因为在我看来它没有得到优化。恕我直言,使用 .remove() 更快。
  • @VisgeanSkeloru 我不同意.remove()“快得多”。我在下面发布了一个答案来解决这个问题(在评论框中很难格式化代码)。
  • 是的,你是对的,我的错误,我没有考虑复制列表所需的时间......我只是想从列表中删除一个元素而不是创建新元素列表...我也玩过复制模块,实际上 [:] 似乎是最快的复制方式...
  • 循环总是以某种形式使用,例如。 remove 使用循环,尽管在 c 级别。
【解决方案2】:
>>> suits = ["h","c", "d", "s"]
>>> noclubs = list(suits)
>>> noclubs.remove("c")
>>> noclubs
['h', 'd', 's']

如果您不需要单独的noclubs

>>> suits = ["h","c", "d", "s"]
>>> suits.remove("c")

【讨论】:

  • 这里需要注意的是,list.remove(x) 仅删除列表中值等于 x 的 第一项。如果没有这样的项目,它会引发 ValueError list comprehension method 会删除 x 的所有实例,如果值不存在则不会引发错误。
【解决方案3】:

这个问题已经得到解答,但我想解决使用列表理解比使用 .remove() 慢得多的评论。

我机器上的一些配置文件(notebook 使用 Python 3.6.9)。

x = ['a', 'b', 'c', 'd']

%%timeit
y = x[:]  # fastest way to copy
y.remove('c')

1000000 loops, best of 3: 203 ns per loop

%%timeit
y = list(x)  # not as fast copy
y.remove('c')

1000000 loops, best of 3: 274 ns per loop

%%timeit
y = [n for n in x if n != 'c']  # list comprehension

1000000 loops, best of 3: 362 ns per loop

%%timeit
i = x.index('c')
y = x[:i] + x[i + 1:]

1000000 loops, best of 3: 375 ns per loop

如果您使用最快的方式来复制列表(这不是很可读),您将比使用列表推导式快 45%。但是,如果您使用 list() 类(更常见且 Pythonic)复制列表,那么您将比使用列表理解慢 25%。

真的,一切都很快。我认为可以说.remove() 比列出列表理解技术更具可读性,但它不一定更快,除非您有兴趣放弃重复的可读性。

在这种情况下,列表推导式的最大优势在于它更加简洁(即,如果您有一个出于某种原因从给定列表中删除元素的函数,则可以在 1 行中完成,而另一行方法需要 3 行代码。)有时单行代码非常方便(尽管它们通常以牺牲一些可读性为代价)。此外,当您实际上不知道要删除的元素是否实际上在开始的列表中时,使用列表推导会表现出色。虽然.remove() 将抛出ValueError,但列表理解将按预期运行。

【讨论】:

  • 另外,请注意列表解析解决方案将删除所有“c”字符,而 remove() 将仅删除第一个。
【解决方案4】:

您可以使用过滤器(或来自 itertools 的 ifilter)

suits = ["h","c", "d", "s"]
noclubs = filter(lambda i: i!='c', suits)

您也可以使用列表结构进行过滤

suits = ["h","c", "d", "s"]
noclubs = [ i for i in suits if i!='c' ]

【讨论】:

  • noclubs = filter(lambda i: i!='c', suits) 给我返回一个过滤器对象,不是列表,需要强制转换为列表
  • 是的,在python3中你必须将它转换为list,在python2中它直接作为list返回。问题是从 2013 年开始的。
【解决方案5】:

不使用 for 循环或 lambda 函数并保留顺序:

suits = ["h","c", "d", "s"]
noclubs = suits[:suits.index("c")]+suits[suits.index("c")+1:]

我知道它在内部仍然会使用循环,但至少你不必在外部使用它们。

【讨论】:

  • 这个方法不错,但不如用suits[:]复制数组然后用.remove()删除元素快。
【解决方案6】:

如果顺序不重要,可以使用集合操作:

suits = ["h", "c", "d", "s"]
noclubs = list(set(suits) - set(["c"]))
# note no order guarantee, the following is the result here:
# noclubs -> ['h', 's', 'd']

【讨论】:

    【解决方案7】:

    一种可能性是使用filter

    >>> import operator
    >>> import functools
    
    >>> suits = ["h", "c", "d", "s"]
    
    >>> # Python 3.x
    >>> list(filter(functools.partial(operator.ne, 'c'), suits))
    ['h', 'd', 's']
    
    >>> # Python 2.x
    >>> filter(functools.partial(operator.ne, 'c'), suits)
    ['h', 'd', 's']
    

    这里也可以使用'c'__ne__ 方法来代替partial

    >>> list(filter('c'.__ne__, suits))
    ['h', 'd', 's']
    

    但是,后一种方法被认为不是非常 Pythonic(通常您不应该直接使用特殊方法 - 以双下划线开头),如果列表包含混合类型,它可能会给出奇怪的结果,但它可能比partial 方法快一点。

    suits = ["h", "c", "d", "s"]*200   # more elements for more stable timings
    %timeit list(filter('c'.__ne__, suits))
    # 164 µs ± 5.98 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
    %timeit list(filter(functools.partial(operator.ne, 'c'), suits))
    # 337 µs ± 13.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
    %timeit list(filter(lambda x: x != 'c', suits))
    # 410 µs ± 13.7 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
    %timeit [x for x in suits if x != "c"]
    181 µs ± 465 ns per loop (mean ± std. dev. of 7 runs, 1000 loops each)
    

    Python 3.5.2 使用 IPython 的魔法 %timeit 命令测试。

    【讨论】:

      【解决方案8】:

      如果您想要删除 特定 元素(而不仅仅是过滤)很重要,那么您需要接近以下内容:

      noclubs = [x for i, x in enumerate(suits) if i != suits.index('c')]
      

      如果您的问题确实与扑克牌有关,您也可以考虑在此处使用set 以在语义上更正确。

      【讨论】:

      • 请注意,此答案仅计算 first 出现'c' 的索引,并对原始列表中的每个元素进行计算。因此,如果原件包含多个待删除的'c',则该功能将不起作用。即使它只包含一个,它也会很慢。最好只比较 if x != 'c' 的值。
      • @MSeifert,我相信这是我回答的重点,创建一个删除 specific 元素的新列表,而不是过滤与某个谓词匹配的所有内容。我同意这可能会更有效。
      【解决方案9】:

      不幸的是,默认情况下,Python 中似乎没有这样的东西。

      有几个答案,但我想添加一个使用迭代器。如果可以接受就地更改,那将是最快的。如果您不想更改原始内容而只想遍历过滤后的集合,这应该非常快:

      实施:

      def without(iterable, remove_indices):
          """
          Returns an iterable for a collection or iterable, which returns all items except the specified indices.
          """
          if not hasattr(remove_indices, '__iter__'):
              remove_indices = {remove_indices}
          else:
              remove_indices = set(remove_indices)
          for k, item in enumerate(iterable):
              if k in remove_indices:
                  continue
              yield item
      

      用法:

      li = list(range(5))
      without(li, 3)             
      # <generator object without at 0x7f6343b7c150>
      list(without(li, (0, 2)))  
      # [1, 3, 4]
      list(without(li, 3))       
      # [0, 1, 2, 4]
      

      所以它是一个生成器 - 您需要调用 list 或其他东西来使其永久化。

      如果您只想删除单个索引,当然可以使用 k == remove_index 而不是集合来加快速度。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-11-02
        • 1970-01-01
        • 1970-01-01
        • 2015-10-10
        • 1970-01-01
        • 1970-01-01
        • 2011-06-01
        相关资源
        最近更新 更多