【问题标题】:Access multiple elements of list knowing their index访问列表的多个元素知道它们的索引
【发布时间】:2013-08-18 19:00:24
【问题描述】:

我需要从给定列表中选择一些元素,知道它们的索引。假设我想创建一个新列表,其中包含来自给定列表 [-2, 1, 5, 3, 8, 5, 6] 的索引为 1、2、5 的元素。我所做的是:

a = [-2,1,5,3,8,5,6]
b = [1,2,5]
c = [ a[i] for i in b]

有没有更好的方法呢?类似 c = a[b] 的东西?

【问题讨论】:

标签: python python-3.x list indexing element


【解决方案1】:

你可以使用operator.itemgetter:

from operator import itemgetter 
a = [-2, 1, 5, 3, 8, 5, 6]
b = [1, 2, 5]
print(itemgetter(*b)(a))
# Result:
(1, 5, 5)

或者你可以使用numpy:

import numpy as np
a = np.array([-2, 1, 5, 3, 8, 5, 6])
b = [1, 2, 5]
print(list(a[b]))
# Result:
[1, 5, 5]

但实际上,您当前的解决方案很好。它可能是所有这些中最整洁的。

【讨论】:

  • +1 提到c = [a[i] for i in b] 非常好。请注意,如果 b 的元素少于 2 个,itemgetter 解决方案将不会做同样的事情。
  • Side 注意:在多进程中工作时使用 itemgetter 不起作用。 Numpy 在多进程中效果很好。
  • 附加注释,a[b]仅在 anumpy 数组时起作用,即您使用 numpy 函数创建它。跨度>
  • 我已经对非 numpy 选项进行了基准测试,并且 itemgetter 似乎是最快的,甚至比使用 Python 3.44 简单地在括号内键入所需的索引还要快
  • @citizen2077,你能举一个你描述的语法的例子吗?
【解决方案2】:

替代方案:

>>> map(a.__getitem__, b)
[1, 5, 5]

>>> import operator
>>> operator.itemgetter(*b)(a)
(1, 5, 5)

【讨论】:

  • 第一个问题是__getitem__ 似乎不可比较,例如如何映射项目的类型? map(type(a.__getitem__), b)
  • @alancalvitti,lambda x: type(a.__getitem__(x)), b。在这种情况下使用[..] 更紧凑:lambda x: type(a[x]), b
  • 只需转换回列表:list(map(a.__getitem__, b))
【解决方案3】:

另一种解决方案可能是通过 pandas 系列:

import pandas as pd

a = pd.Series([-2, 1, 5, 3, 8, 5, 6])
b = [1, 2, 5]
c = a[b]

如果需要,您可以将 c 转换回列表:

c = list(c)

【讨论】:

    【解决方案4】:

    比较提供的五个答案的执行时间的基本且不是非常广泛的测试:

    def numpyIndexValues(a, b):
        na = np.array(a)
        nb = np.array(b)
        out = list(na[nb])
        return out
    
    def mapIndexValues(a, b):
        out = map(a.__getitem__, b)
        return list(out)
    
    def getIndexValues(a, b):
        out = operator.itemgetter(*b)(a)
        return out
    
    def pythonLoopOverlap(a, b):
        c = [ a[i] for i in b]
        return c
    
    multipleListItemValues = lambda searchList, ind: [searchList[i] for i in ind]
    

    使用以下输入:

    a = range(0, 10000000)
    b = range(500, 500000)
    

    简单的 python 循环是最快的,紧随其后的是 lambda 操作,mapIndexValues 和 getIndexValues 与 numpy 方法一直非常相似,在将列表转换为 numpy 数组后明显变慢。如果数据已经在 numpy 数组中,则使用 numpy.array 的 numpyIndexValues 方法移除转换是最快的。

    numpyIndexValues -> time:1.38940598 (when converted the lists to numpy arrays)
    numpyIndexValues -> time:0.0193445 (using numpy array instead of python list as input, and conversion code removed)
    mapIndexValues -> time:0.06477512099999999
    getIndexValues -> time:0.06391049500000001
    multipleListItemValues -> time:0.043773591
    pythonLoopOverlap -> time:0.043021754999999995
    

    【讨论】:

    • 我不知道您使用的是什么 Python 解释器,但第一种方法 numpyIndexValues 不起作用,因为 ab 的类型为 range。我猜你想先将ab 转换为numpy.ndarrays
    • @strpeter 是的,我没有将苹果与苹果进行比较,我在 numpyIndexValues 的测试用例中创建了 numpy 数组作为输入。我现在已经解决了这个问题,并且都使用相同的列表作为输入。
    【解决方案5】:

    这里有一个更简单的方法:

    a = [-2,1,5,3,8,5,6]
    b = [1,2,5]
    c = [e for i, e in enumerate(a) if i in b]
    

    【讨论】:

      【解决方案6】:

      我确信这已经被考虑过:如果 b 中的索引数量很小且恒定,则可以将结果写为:

      c = [a[b[0]]] + [a[b[1]]] + [a[b[2]]]
      

      如果索引本身是常量,甚至更简单......

      c = [a[1]] + [a[2]] + [a[5]]
      

      或者如果有连续的索引范围...

      c = a[1:3] + [a[5]]
      

      【讨论】:

      • 谢谢你提醒我[a] + [b] = [a, b]
      • 请注意,+ 会复制列表。您可能希望extend 原地修改列表。
      【解决方案7】:

      我的答案不使用 numpy 或 python 集合。

      查找元素的一种简单方法如下:

      a = [-2, 1, 5, 3, 8, 5, 6]
      b = [1, 2, 5]
      c = [i for i in a if i in b]
      

      缺点:此方法可能不适用于较大的列表。对于较大的列表,建议使用 numpy。

      【讨论】:

      • 无需迭代a[a[i] for i in b]
      • 这种方法在其他任何情况下都不起作用。如果a 里面还有另外 5 个呢?
      • IMO,使用 sets 更快地完成这种交叉路口
      • 如果您担心 IndexErrors 如果 b 的数字超过 a 的大小,请尝试 [a[i] if i<len(a) else None for i in b]
      【解决方案8】:

      一种pythonic方式:

      c = [x for x in a if a.index(x) in b]
      

      【讨论】:

      • 我会说这甚至比 OP 的示例更不“pythonic”——您已经设法将他们的 O(n) 解决方案变成了 O(n^2) 解决方案,同时还将代码的长度几乎增加了一倍.您还需要注意,如果列表包含对象将模糊或部分相等,则该方法将失败,例如如果a 包含float('nan'),这将总是引发ValueError
      【解决方案9】:

      列表推导式显然是最直接和最容易记住的——除了非常pythonic!

      无论如何,在提出的解决方案中,它并不是最快的(我已经使用 Python 3.8.3 在 Windows 上运行了测试):

      import timeit
      from itertools import compress
      import random
      from operator import itemgetter
      import pandas as pd
      
      __N_TESTS__ = 10_000
      
      vector = [str(x) for x in range(100)]
      filter_indeces = sorted(random.sample(range(100), 10))
      filter_boolean = random.choices([True, False], k=100)
      
      # Different ways for selecting elements given indeces
      
      # list comprehension
      def f1(v, f):
         return [v[i] for i in filter_indeces]
      
      # itemgetter
      def f2(v, f):
         return itemgetter(*f)(v)
      
      # using pandas.Series
      # this is immensely slow
      def f3(v, f):
         return list(pd.Series(v)[f])
      
      # using map and __getitem__
      def f4(v, f):
         return list(map(v.__getitem__, f))
      
      # using enumerate!
      def f5(v, f):
         return [x for i, x in enumerate(v) if i in f]
      
      # using numpy array
      def f6(v, f):
         return list(np.array(v)[f])
      
      print("{:30s}:{:f} secs".format("List comprehension", timeit.timeit(lambda:f1(vector, filter_indeces), number=__N_TESTS__)))
      print("{:30s}:{:f} secs".format("Operator.itemgetter", timeit.timeit(lambda:f2(vector, filter_indeces), number=__N_TESTS__)))
      print("{:30s}:{:f} secs".format("Using Pandas series", timeit.timeit(lambda:f3(vector, filter_indeces), number=__N_TESTS__)))
      print("{:30s}:{:f} secs".format("Using map and __getitem__", timeit.timeit(lambda: f4(vector, filter_indeces), number=__N_TESTS__)))
      print("{:30s}:{:f} secs".format("Enumeration (Why anyway?)", timeit.timeit(lambda: f5(vector, filter_indeces), number=__N_TESTS__)))
      

      我的结果是:

      列表理解:0.007113 秒
      Operator.itemgetter :0.003247 秒
      使用 Pandas 系列:2.977286 秒
      使用地图和 getitem:0.005029 秒
      枚举(为什么?):0.135156 秒
      Numpy:0.157018 秒

      【讨论】:

        【解决方案10】:

        静态索引和小列表?

        不要忘记,如果列表很小且索引不变,如您的示例,有时最好使用sequence unpacking

        _,a1,a2,_,_,a3,_ = a
        

        性能好很多,还可以省一行代码:

         %timeit _,a1,b1,_,_,c1,_ = a
        10000000 loops, best of 3: 154 ns per loop 
        %timeit itemgetter(*b)(a)
        1000000 loops, best of 3: 753 ns per loop
         %timeit [ a[i] for i in b]
        1000000 loops, best of 3: 777 ns per loop
         %timeit map(a.__getitem__, b)
        1000000 loops, best of 3: 1.42 µs per loop
        

        【讨论】:

          猜你喜欢
          • 2018-02-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-10-13
          • 2020-10-07
          • 2020-01-15
          • 2017-04-18
          • 1970-01-01
          相关资源
          最近更新 更多