如何使用给定的索引获取 Python 列表的子列表?

55

我有一个Python列表,比如说a=[0,1,2,3,4,5,6]。我还有一个索引列表,比如说b=[0,2,4,5]。如何获取a中的索引在b中的元素的列表?

8个回答

70
你可以使用列表推导式来获取该列表:
c = [a[index] for index in b]
print c

这相当于:

c= []
for index in b:
    c.append(a[index])
print c

输出:

[0,2,4,5]

注意:

请记住some_list[index]是用于访问列表中特定索引处的元素的标记符号。


34

有所不同的东西...

>>> a = range(7)
>>> b = [0,2,4,5]
>>> import operator
>>> operator.itemgetter(*b)(a)
(0, 2, 4, 5)
itemgetter 函数接受一个或多个键作为参数,并返回一个函数,该函数将在其参数中返回给定键的项。因此,在上面的示例中,我们创建一个将返回索引0、索引2、索引4和索引5处项目的函数,然后将该函数应用于a
看起来比等效的列表推导式快得多。
In [1]: import operator

In [2]: a = range(7)

In [3]: b = [0,2,4,5]

In [4]: %timeit operator.itemgetter(*b)(a)
1000000 loops, best of 3: 388 ns per loop

In [5]: %timeit [ a[i] for i in b ]
1000000 loops, best of 3: 415 ns per loop

In [6]: f = operator.itemgetter(*b)

In [7]: %timeit f(a)
10000000 loops, best of 3: 183 ns per loop

关于为什么 itemgetter 更快,原因在于列表推导式需要执行额外的 Python 字节码。
In [3]: def f(a,b): return [a[i] for i in b]

In [4]: def g(a,b): return operator.itemgetter(*b)(a)

In [5]: dis.dis(f)
  1           0 BUILD_LIST               0
              3 LOAD_FAST                1 (b)
              6 GET_ITER
        >>    7 FOR_ITER                16 (to 26)
             10 STORE_FAST               2 (i)
             13 LOAD_FAST                0 (a)
             16 LOAD_FAST                2 (i)
             19 BINARY_SUBSCR
             20 LIST_APPEND              2
             23 JUMP_ABSOLUTE            7
        >>   26 RETURN_VALUE

itemgetter 是一个在 C 中实现的单个调用:

In [6]: dis.dis(g)
  1           0 LOAD_GLOBAL              0 (operator)
              3 LOAD_ATTR                1 (itemgetter)
              6 LOAD_FAST                1 (b)
              9 CALL_FUNCTION_VAR        0
             12 LOAD_FAST                0 (a)
             15 CALL_FUNCTION            1
             18 RETURN_VALUE

1
可能也是最快的解决方案。 - martineau
我没有想到那个。看起来快了很多;我会在ipython中发布我所做的测试。 - chepner
它在某种意义上也相当通用,因为它可以用来从给定键的字典中提取一系列值(这是我最常用它的方法)。 - martineau
切片,偶数:itemgetter(slice(2,5))(a) -> [2, 3, 4]。我已经添加了该函数的(2.x)文档链接。 - chepner
1
列表推导式的开销更大,因为迭代是在Python中设置和执行的。而operator.itemgetter则是在C中完成其工作。 - chepner
显示剩余3条评论

10

如果你是函数式编程的粉丝,你可以使用maplist.__getitem__

>>> a = [0,1,2,3,4,5,6]
>>> b = [0,2,4,5]
>>> map(a.__getitem__, b)
[0, 2, 4, 5]
>>>

虽然在Python中,列表推导式的方法更加规范...


5

许多提出的解决方案会在 b 中包含a中不存在的索引时产生 KeyError 。如果需要,以下代码将跳过无效的索引。

>>> b = [0,2,4,5]
>>> a = [0,1,2,3,4,5,6]
>>> [x for i,x in enumerate(a) if i in b]
[0, 2, 4, 5]
>>> b = [0,2,4,500]
>>> [x for i,x in enumerate(a) if i in b]
[0, 2, 4]

enumerate 产生索引-值对的元组。既然我们既有项目又有它的索引,我们可以检查索引是否存在于 b 中。


4
所有提到的方法以及其他来自Python字典:获取键列表的值列表的一些速度比较:
Python 2.7.11 |Anaconda 2.4.1 (64-bit)| (default, Jan 19 2016, 12:08:31) [MSC v.1500 64 bit (AMD64)] on win32

In[2]: import numpy.random as nprnd
idx = nprnd.randint(1000, size=10000)
l = nprnd.rand(1000).tolist()
from operator import itemgetter
import operator
f = operator.itemgetter(*idx)
%timeit f(l)
%timeit list(itemgetter(*idx)(l))
%timeit [l[_] for _ in idx]  # list comprehension
%timeit map(l.__getitem__, idx)
%timeit list(l[_] for _ in idx)  # a generator expression passed to a list constructor.
%timeit map(lambda _: l[_], idx)  # using 'map'
%timeit [x for i, x in enumerate(l) if i in idx]
%timeit filter(lambda x: l.index(x) in idx, l)  # UPDATE @Kundor: work only for list with unique elements
10000 loops, best of 3: 175 µs per loop
1000 loops, best of 3: 707 µs per loop
1000 loops, best of 3: 978 µs per loop
1000 loops, best of 3: 1.03 ms per loop
1000 loops, best of 3: 1.18 ms per loop
1000 loops, best of 3: 1.86 ms per loop
100 loops, best of 3: 12.3 ms per loop
10 loops, best of 3: 21.2 ms per loop

所以最快的方法是:f = operator.itemgetter(*idx); f(l)

过滤行没有做正确的事情。例如,如果 l[1,2,3,2,1,2,3,2],而 idx[0,1,4,5],那么过滤方法将给出 [1, 2, 2, 1, 2, 2],而所有其他方法将(正确地)给出 [1,2,1,2]。此外,为了保持一致性,您应该在 map 调用中包装 list() - Nick Matteo
@kundor 是的,在列表中存在非唯一值的情况下,您关于 filter 的看法是正确的。 - Sklavit
@Kundor,关于在list中包装 - 这不是必要的,因为这是Python 2.7。 - Sklavit
那么为什么要用list包装filter呢? - Nick Matteo

4
使用 numpy.asarray。Numpy 允许通过索引列表获取数组的子数组。
>>> import numpy as np
>>> a = [0,10,20,30,40,50,60]
>>> b = [0,2,4,5]
>>> res = np.asarray(a)[b].tolist()
>>> res
[0, 20, 40, 50]

4
使用列表推导式,这应该可以工作 -
li = [a[i] for i in b]

测试这个 -

>>> a = [0,10,20,30,40,50,60]
>>> b = [0,2,4,5]
>>> li = [a[i] for i in b]
>>> li
[0, 20, 40, 50]

1
如果性能对您很重要,那么另一种更好的选择是-它绝不是最Pythonic的,但我非常确定它是最有效的。
>>> list(filter(lambda x: a.index(x) in b, a))
[0, 2, 4, 5]

注意:在Python 2中,您不需要转换为list。但是在Python 3及以后版本中需要(如果有任何未来访问者可能遇到类似问题)。

由于OP正在使用Python 2.7,您不需要将“filter”放在“list”中。这仅适用于Python 3.x。 - user2555451
@iCodez 谢谢,我已经扩展了我的答案。我将其转换为list以测试我的解决方案(我正在使用Python 3)-但是我认为我会将我的解决方案留在那里,因为它不会在Python 2中引起错误,同时适用于更广泛的Python 3受众。 - anon582847382

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接