簡體   English   中英

從給定索引數組的 Python 列表中提取子列表的最快方法

[英]Fastest method for extracting sub-list from Python list given array of indexes

我有任何類型的對象的大型 Python 列表l ,我還有另一個指向列表l某些元素的整數索引的大型列表i (甚至NumPy數組)。

問題是創建另一個列表l2的最快(最有效)方法是什么,該列表包含l元素和來自i索引。

最簡單的方法是做一個列表理解:

l2 = [l[si] for si in i]
# Use np.nditer(i) instead of i, for NumPy array case

但這是最快的方法嗎?

列表理解是一個 Python 循環,所以對於大型列表來說可能會很慢,也許標准庫中有一些用高效C編寫的內置 Python 方法來實現這個任務? 或者也許在NumPy有這樣的方法可以通過 numpy 數組索引 Python 的列表?

也許標准 python 庫中有一些簡單而快速的函數可以對 NumPy 的np.take做同樣的事情,就像下面的假想代碼:

import listtools
l2 = listtools.take(l, indexes)

您可以通過使用支持批量查找的operator.itemgetter獲得較小的加速(在下面的示例中約為 25%):

>>> import string
>>> import random
>>> import operator as op
>>> from timeit import timeit

# create random lists
>>> l = [random.choice([*string.ascii_letters,*range(100)]) for _ in range(1000000)]
>>> i = [random.randint(0,999999) for _ in range(300000)]

# timings
>>> timeit(lambda:[l[si] for si in i],number=100)
3.0997245000035036
>>> timeit(lambda:list(map(l.__getitem__,i)),number=100)
2.892384369013598
>>> timeit(lambda:list(op.itemgetter(*i)(l)),number=100)
2.1787672539940104

眾所周知, NumPy數組還可以通過dtype = np.object_用於存儲和處理任意 Python 對象。

所以我決定測量 NumPy 與普通 Python 相比的使用速度。 同樣正如我在我的問題中提到的,我還想解決索引是 numpy 整數數組的情況。

下一個代碼測量不同的情況,我們是否需要將源列表轉換為 numpy 數組以及是否也應該轉換結果。

在線試試吧!

import string
from timeit import timeit
import numpy as np
np.random.seed(0)

letters = np.array(list(string.ascii_letters), dtype = np.object_)
nl = letters[np.random.randint(0, len(letters), size = (10 ** 6,))]
l = nl.tolist()
ni = np.random.permutation(np.arange(nl.size, dtype = np.int64))
i = ni.tolist()

pyt = timeit(lambda: [l[si] for si in i], number = 10)
print('python:', round(pyt, 3), flush = True)

for l_from_list in [True, False]:
    for i_from_list in [True, False]:
        for l_to_list in [True, False]:
            def Do():
                cl = np.array(l, dtype = np.object_) if l_from_list else nl
                ci = np.array(i, dtype = np.int64) if i_from_list else ni
                res = cl[ci]
                res = res.tolist() if l_to_list else res
                return res
            ct = timeit(lambda: Do(), number = 10)
            print(
                'numpy:', 'l_from_list', l_from_list, 'i_from_list', i_from_list, 'l_to_list', l_to_list,
                'time', round(ct, 3), 'speedup', round(pyt / ct, 2), flush = True
            )

輸出:

python: 2.279
numpy: l_from_list True  i_from_list True  l_to_list True  time 2.924 speedup 0.78
numpy: l_from_list True  i_from_list True  l_to_list False time 2.805 speedup 0.81
numpy: l_from_list True  i_from_list False l_to_list True  time 1.457 speedup 1.56
numpy: l_from_list True  i_from_list False l_to_list False time 1.312 speedup 1.74
numpy: l_from_list False i_from_list True  l_to_list True  time 2.352 speedup 0.97
numpy: l_from_list False i_from_list True  l_to_list False time 2.209 speedup 1.03
numpy: l_from_list False i_from_list False l_to_list True  time 0.894 speedup 2.55
numpy: l_from_list False i_from_list False l_to_list False time 0.75  speedup 3.04

所以我們可以看到,如果我們將所有列表存儲為 numpy 數組,那么我們將獲得3x加速! 但是如果只有索引是一個 numpy 數組,那么我們的加速僅為1.56x ,這也非常好。 如果所有內容都必須從列表來回轉換,那么我們的速度提高了0.78x ,這意味着我們會放慢速度,因此如果我們只使用列表,那么通過 numpy 進行索引是沒有幫助的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM