【问题标题】:Best/Fast way to remove a column from a Python 2D list从 Python 2D 列表中删除列的最佳/快速方法
【发布时间】:2019-06-27 10:27:08
【问题描述】:

我在 python 中有一个列表列表(所有列表的大小都相同),如下所示:

A = [[1,2,3,4],['a','b','c','d'] , [12,13,14,15]]

我想删除一些列(所有列表的第 i 个元素)。

如果没有for 语句,有什么方法可以做到这一点?

【问题讨论】:

  • 用 numpy 是的。否则,您至少需要 1 个for
  • 为什么要在没有 for 语句的情况下这样做?当然你可以用 while 循环替换 for 循环,但这只会降低你的代码的可读性
  • 从根本上说,即使 numpy 也使用循环实现。虽然有点迂腐,但我们也可以在这里使用while 循环来实现

标签: python python-3.x list filter


【解决方案1】:

如前所述,没有循环就无法做到这一点。但是,这里使用内置函数是一种不显式使用任何循环的函数式方法:

In [24]: from operator import itemgetter

In [25]: def remove_col(arr, ith):
    ...:     itg = itemgetter(*filter((ith).__ne__, range(len(arr[0]))))
    ...:     return list(map(list, map(itg, arr)))
    ...: 

演示:

In [26]: remove_col(A, 1)
Out[26]: [[1, 3, 4], ['a', 'c', 'd'], [12, 14, 15]]

In [27]: remove_col(A, 3)
Out[27]: [[1, 2, 3], ['a', 'b', 'c'], [12, 13, 14]]

请注意,如果您只返回map(itg, arr),而不是list(map(list, map(itg, arr))),它将给您预期的结果,但作为迭代器的迭代器而不是列表的列表。在这种情况下,就内存和运行时间而言,这将是一种更优化的方法。

另外,使用循环是我这样做的方式:

In [31]: def remove_col(arr, ith):
    ...:     return [[j for i,j in enumerate(sub) if i != ith] for sub in arr]

令人惊讶的是(如果您相信 C 的强大功能,则不会 :))函数式方法对于大型数组来说甚至更快。

In [41]: arr = A * 10000

In [42]: %timeit remove_col_functional(arr, 2)
8.42 ms ± 37.9 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [43]: %timeit remove_col_list_com(arr, 2)
23.7 ms ± 165 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

# And if in functional approach you just return map(itg, arr)
In [47]: %timeit remove_col_functional_iterator(arr, 2)
1.48 µs ± 4.71 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

【讨论】:

  • 真的喜欢你的“使用循环”方法......几乎就像I might come up with。 :-P
  • 嗯,没想到。虽然,当我测试它时,差异并不那么显着。此外,恕我直言,迭代器创建的时间有点愚蠢,你无法真正比​​较它们。
  • @tobias_k 当然,它只是作为上述解释的教授。关于差异,这里也没有那么戏剧化,不要感到惊讶,因为这是 C 的力量和 leaky abstractions 的法则。
【解决方案2】:

另一个使用列表理解的变体,enumerate

>>> A = [[1,2,3,4],['a','b','c','d'] , [12,13,14,15]]
>>> k = 2
>>> [[x for i, x in enumerate(a) if i != k] for a in A]
[[1, 2, 4], ['a', 'b', 'd'], [12, 13, 15]]

而且,是的,其中包含 for 一词(甚至两次!),但性能不应与任何其他方法不同(不过,numpy 可能更快)。

【讨论】:

    【解决方案3】:

    numpy 能够删除整列:

    import numpy
    
    A = [[1,2,3,4],['a','b','c','d'] , [12,13,14,15]]
    
    na = numpy.array(A)
    
    print(na[:,:-1])   # remove last column
    print(na[:,1:])    # remove first column
    
    print(numpy.concatenate((na[:,:2],na[:,3:]),axis=1)) # build from 2 slices: remove third column
    

    结果(简单起见:所有数据都已转换为字符串,不涉及dtype):

    [['1' '2' '3']
     ['a' 'b' 'c']
     ['12' '13' '14']]
    
    [['2' '3' '4']
     ['b' 'c' 'd']
     ['13' '14' '15']]
    
    [['1' '2' '4']
     ['a' 'b' 'd']
     ['12' '13' '15']]
    

    【讨论】:

      【解决方案4】:

      您可以轻松使用list comprehensionslices

      A = [[1,2,3,4],['a','b','c','d'] , [12,13,14,15]]
      k = 1
      
      B = [l[:k]+l[k+1:] for l in A]
      
      print(B) # >> returns [[1, 3, 4], ['a', 'c', 'd'], [12, 14, 15]]
      

      【讨论】:

      • 这还有for,也许你可以包括地图?
      • 列表理解并不比映射慢,而且它们更具可读性......而且我认为提问者想避免使用for循环,而不是任何O(1)循环......
      • @olinox14 我同意“无 for 循环”的请求是毫无意义的,因为所有其他方法也有隐藏在里面的循环,但什么是“O(1) 循环” ?
      • 这是衡量算法复杂度的标准方法,看看herehere
      【解决方案5】:

      如果你精通zip,我认为你可以在没有for 的情况下做到这一点(这是我最喜欢的“hack”):

      A = [[1, 2, 3, 4], ['a', 'b', 'c', 'd'], [12, 13, 14, 15]]
      B = list(zip(*A))
      B.pop(i)
      C = list(map(list, zip(*B)))
      

      结果(i = 2):

      [[1, 2, 4], ['a', 'b', 'd'], [12, 13, 15]]
      

      当然,map 是列表理解的替代方案:

      B = list(map(lambda l: l[:i] + l[i + 1:], A))
      

      【讨论】:

      • 我很确定与标准压缩相比,性能会受到影响,因为创建了所有临时对象。但我可能是错的。并且不推荐map(lambda
      • @Jean-FrançoisFabre 当然你是对的,但 zipmap 对象应该与生成器相当(但不是列表推导)。
      猜你喜欢
      • 1970-01-01
      • 2013-06-05
      • 1970-01-01
      • 1970-01-01
      • 2010-12-13
      • 2011-08-10
      • 2020-10-04
      • 2016-05-14
      • 1970-01-01
      相关资源
      最近更新 更多