Самый эффективный способ обратить вспять массив


276

Хотите верьте, хотите нет, но после профилирования моего текущего кода повторяющаяся операция возврата массивов клочков съела гигантский кусок времени выполнения. Сейчас у меня есть общий метод, основанный на представлении:

reversed_arr = arr[::-1]

Есть ли какой-то другой способ сделать это более эффективно, или это просто иллюзия из-за моей одержимости нереальным выступлением?


27
Э-э ... arr[::-1]просто возвращает перевернутое представление. Это так быстро, как вы можете получить, и не зависит от количества элементов в массиве, поскольку это просто меняет шаги. Является ли то, что вы изменили на самом деле массивом?
Джо Кингтон

да, действительно, arrэто массив NumPy.
nye17

12
Хммм ... Ну, на моем ноутбуке это занимает около 670 наносекунд, независимо от длины массива. Если это ваше узкое место, вам может потребоваться переключить языки ... Я уверен, что вы не найдете более быстрого способа обращения к массиву. В любом случае, удачи!
Джо Кингтон

6
Ну, вы обязательно должны запустить его внутри цикла? В некоторых случаях лучше создать массив с миллионами элементов, а затем работать со всем массивом. Даже если вы используете метод конечных разностей или что-то подобное, где результат зависит от предыдущего результата, иногда вы можете сделать это. (Акцент иногда ...) Во всяком случае, если скорость является основной целью, Фортран по-прежнему король. f2pyтвой друг! Часто стоит написать критичные к производительности части алгоритма (особенно в научных вычислениях) на другом языке и вызвать его из python. Удачи!
Джо Кингтон

1
@berto. Это медленнее, так как это обертка для arr[::-1]: github.com/numpy/numpy/blob/master/numpy/lib/twodim_base.py . Ищите def flipud. Функция буквально в четыре строки.
Безумный физик

Ответы:


240

При создании reversed_arrвы создаете представление в исходном массиве. Затем вы можете изменить исходный массив, и представление обновится, чтобы отразить изменения.

Вы воссоздаете представление чаще, чем нужно? Вы должны быть в состоянии сделать что-то вроде этого:

arr = np.array(some_sequence)
reversed_arr = arr[::-1]

do_something(arr)
look_at(reversed_arr)
do_something_else(arr)
look_at(reversed_arr)

Я не эксперт в NumPy, но похоже, что это будет самый быстрый способ сделать все в NUMPY. Если это то, что вы уже делаете, я не думаю, что вы можете улучшить это.

PS Отличная дискуссия о numpy взглядах здесь:

Посмотреть на массив NumPy?


Помогает ли это создать объект среза, а затем повторно использовать его во многих массивах?
эндолит

1
На самом деле я только что проверил это и не вижу никакой разницы с объектом слайса, созданным вне цикла. (Ой, подождите, это очень немного быстрее. Повторно 43,4 мс против 44,3 мс для цикла 1000000)
эндолит

Что look_atдолжна делать функция?
mrgloom

1
@mrgloom Предполагается, что представляет собой любую задачу, которая смотрит на данные. Целью примера было показать, что reversed_arrпредставление все еще можно использовать после изменения базовых данных. Запись новых значений в массив не лишает законной силы представление. На самом деле вы также можете использовать представление для записи новых значений в массив. reversed_arr[0] = 99установил бы последний элемент в массиве на 99, так же как arr[-1] = 99и.
Steveha

60

Как упоминалось выше, на a[::-1]самом деле создается только представление, поэтому это операция с постоянным временем (и, как таковая, не занимает больше времени при увеличении массива). Если вам нужно, чтобы массив был смежным (например, потому что вы выполняете с ним много векторных операций), ascontiguousarrayэто примерно так же быстро, как flipup/ fliplr:

введите описание изображения здесь


Код для генерации сюжета:

import numpy
import perfplot


perfplot.show(
    setup=lambda n: numpy.random.randint(0, 1000, n),
    kernels=[
        lambda a: a[::-1],
        lambda a: numpy.ascontiguousarray(a[::-1]),
        lambda a: numpy.fliplr([a])[0],
    ],
    labels=["a[::-1]", "ascontiguousarray(a[::-1])", "fliplr"],
    n_range=[2 ** k for k in range(25)],
    xlabel="len(a)",
    logx=True,
    logy=True,
)

Для perfplot требуется как минимум Python 3.6, потому что он использует f-строки (Literal String Interpolation)
пятое

42

Потому что это, кажется, еще не помечено как ответ ... Ответ Томаса Арилдсена должен быть правильным: просто используйте

np.flipud(your_array) 

если это массив 1d (массив столбцов).

С матрицами делаю

fliplr(matrix)

если вы хотите перевернуть строки и flipud(matrix)перевернуть столбцы. Нет необходимости превращать массив из 1d-столбца в двухмерный массив строк (матрица с одним None-слоем), а затем переворачивать его.


38

np.fliplr() переворачивает массив слева направо.

Обратите внимание, что для 1d-массивов вам нужно немного его обмануть:

arr1d = np.array(some_sequence)
reversed_arr = np.fliplr([arr1d])[0]

34
reversed_arr = np.flipud(arr1d)кажется, работает напрямую.
Томас Арилдсен

3

Я остановлюсь на более раннем ответе о np.fliplr(). Вот некоторый код, который демонстрирует создание 1d-массива, преобразование его в 2-мерный массив, переворачивание и последующее преобразование обратно в 1-мерный массив. time.clock()будет использоваться для хранения времени, которое представлено в секундах.

import time
import numpy as np

start = time.clock()
x = np.array(range(3))
#transform to 2d
x = np.atleast_2d(x)
#flip array
x = np.fliplr(x)
#take first (and only) element
x = x[0]
#print x
end = time.clock()
print end-start

С заявлением на печать без комментариев:

[2 1 0]
0.00203907123594

С заявлением о печати закомментировано:

5.59799927506e-05

Так что, с точки зрения эффективности, я думаю, что это достойно. Для тех из вас, кто любит делать это в одной строке, вот эта форма.

np.fliplr(np.atleast_2d(np.array(range(3))))[0]

3
Сроки чего-то с таким маленьким массивом довольно бесполезны. Если вы хотите сравнить вещи, было бы лучше использовать что-то, что занимает некоторое время, например, 3000 или даже больше элементов.
Барабас

0

Подробно о том, что сказали другие, приведу короткий пример.

Если у вас есть одномерный массив ...

>>> import numpy as np
>>> x = np.arange(4) # array([0, 1, 2, 3])
>>> x[::-1] # returns a view
Out[1]: 
array([3, 2, 1, 0])

Но если вы работаете с 2D-массивом ...

>>> x = np.arange(10).reshape(2, 5)
>>> x
Out[2]:
array([[0, 1, 2, 3, 4],
       [5, 6, 7, 8, 9]])

>>> x[::-1] # returns a view:
Out[3]: array([[5, 6, 7, 8, 9],
               [0, 1, 2, 3, 4]])

Это на самом деле не переворачивает Матрицу.

Следует использовать np.flip для реверсирования элементов

>>> np.flip(x)
Out[4]: array([[9, 8, 7, 6, 5],
               [4, 3, 2, 1, 0]])

Если вы хотите распечатать элементы матрицы по одному, используйте плоскую и флип

>>> for el in np.flip(x).flat:
>>>     print(el, end = ' ')
9 8 7 6 5 4 3 2 1 0

-1

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

b = numpy.flipud(numpy.array(a.split(),float))

Где flipud для 1d arra

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.