Эффективный множественный, произвольный доступ к индексу в кортеже Python? - PullRequest
4 голосов
/ 30 августа 2011

У меня длинный кортеж Python t. Я хотел бы получить элементы с индексами i1, i2, ..., iN из t настолько эффективно, насколько это возможно. Какой лучший способ?

Один из подходов:

(1)    result = [t[j] for j in (i1, i2, ..., iN)]

но это может вызвать N отдельных поисков в кортеже. Есть ли более быстрый способ? Когда Python делает срезы, как это:

(2)    result = t[1:M:3]

Я предполагаю, что он не выполняет M / 3 отдельный поиск. (Может быть, он использует битовую маску и выполняет одну операцию копирования?) Есть ли какой-то способ для меня извлечь выгоду из того, что Python делает в (2), чтобы сделать мой фрагмент произвольного индекса в одной копии?

Спасибо.

Ответы [ 5 ]

7 голосов
/ 31 августа 2011

Если вы выполняете кучу одинаковых поисков, возможно, стоит использовать itemgetter

from operator import itemgetter
mygetter = itemgetter(i1, i2, ..., iN)
for tup in lots_of_tuples:
    result = mygetter(tup)

С одной стороны, затраты на создание элемента не стоит

Быстрый тест в iPython показывает:

In [1]: import random

In [2]: from operator import itemgetter

In [3]: t=tuple(range(1000))

In [4]: idxs = tuple(random.randrange(1000) for i in range(20))

In [5]: timeit [t[i] for i in idxs]
100000 loops, best of 3: 2.09 us per loop

In [6]: mygetter = itemgetter(*idxs)

In [7]: timeit mygetter(t)
1000000 loops, best of 3: 596 ns per loop

Очевидно, что разница будет зависеть от длины кортежа, количества индексов и т. Д.

2 голосов
/ 30 августа 2011

Тот, который вы перечислили, является наиболее оптимальным способом получения элементов из кортежа.Обычно в таких выражениях вас не волнует производительность - это преждевременная оптимизация, и даже если вы это сделали, такие операции уже слишком медленные, даже с оптимизацией, т.е. если вы оптимизируете доступ, сам цикл все равно будет медленным из-заподсчет ссылок на временные переменные и т. д.

Если у вас уже есть проблема с производительностью или она уже является частью загруженного кода процессора, вы можете попробовать несколько вариантов:

1) numpy массивы:

>>> arr = np.array(xrange(2000))
>>> mask = np.array([True]*2000)
>>> mask = np.array([False]*2000)
>>> mask[3] = True
>>> mask[300] = True
>>> arr[mask]
array([  3, 300])

2) Вы можете использовать C API для копирования элементов, используя PyTuple_GET_ITEM, который напрямую обращается к внутреннему массиву, но имейте в виду, что использование C API не тривиально и может ввести многоошибки.

3) Вы можете использовать массивы C с C API, используя, например, интерфейс буфера array.array для склеивания доступа к данным в Python.

4) Вы можете использовать Cython сC-массивы и пользовательский тип Cython для доступа к данным из Python.

5) Вы можете использовать Cython и numpy вместе.

0 голосов
/ 31 августа 2011

1) Вы уверены, что вам нужна операция, чтобы идти быстрее?

2) Другой вариант - operator.itemgetter: возвращает кортеж, выбранный по его индексам:

>>> t = tuple(string.ascii_uppercase)
>>> operator.itemgetter(13,19,4,21,1)(t)
('N', 'T', 'E', 'V', 'B')

Модуль operator реализован на C, поэтому, вероятно, превзойдет цикл Python.

0 голосов
/ 30 августа 2011

Нарезка может быть более эффективной, поскольку она имеет больше ограничений: индекс должен работать линейно на фиксированную величину.Понимание списка может быть абсолютно случайным, поэтому оптимизация невозможна.

Все же опасно делать предположения об эффективности.Попробуйте рассчитать оба пути и посмотрите, есть ли существенная разница.

0 голосов
/ 30 августа 2011

Внутри понимания списка есть неявный цикл for, и я уверен, что он выполняет итерации по значениям кортежей с разумной эффективностью. Я не думаю, что вы можете улучшить понимание списка для эффективности.

Если вам просто нужны значения, вы можете использовать выражение генератора и избежать создания списка для небольшой экономии времени или памяти.

...