Найефективніший спосіб повернути нумерований масив


276

Вірите чи ні, після профілю мого поточного коду повторювана операція реверсії масивного масиву з'їла гігантський шматок часу роботи. Зараз у мене є загальний метод на основі перегляду:

reversed_arr = arr[::-1]

Чи є якийсь інший спосіб зробити це більш ефективно, чи це просто ілюзія моєї одержимості нереалістичним нудотним виконанням?


27
Er ... arr[::-1]просто повертає перевернутий вигляд. Це так швидко, як ви можете отримати, і не залежить від кількості елементів у масиві, оскільки він просто змінює крок. Це те, що ти реверсуєш, насправді нумерований масив?
Джо Кінгтон

так, дійсно, arrце масивний масив.
nye17

12
Гммм ... Ну, на моєму ноутбуці потрібно близько 670 наносекунд незалежно від довжини масиву. Якщо це вузьке місце, можливо, вам доведеться переключити мови ... Я впевнений, що ви не знайдете більш швидкого способу повернення нумерованого масиву. Успіхів, у будь-якому разі!
Джо Кінгтон

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

1
@berto. Це повільніше, оскільки це обгортка для arr[::-1]: github.com/numpy/numpy/blob/master/numpy/lib/twodim_base.py . Шукати def flipud. Функція - буквально чотири рядки.
Божевільний фізик

Відповіді:


239

Під час створення 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)

Я не нудний експерт, але це здається, що це було б найшвидшим способом робити речі в нуме. Якщо це ви вже робите, я не думаю, що ви можете вдосконалити це.

PS Чудове обговорення нудних поглядів тут:

Переглянути нумерований масив?


Чи допомагає це створити об'єкт фрагмента, а потім повторно використовувати його на багатьох масивах?
ендоліт

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-string (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 масиву, перетворення його на 2d масив, гортання його, а потім перетворення назад у 1d масив. 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

Розкриваючи те, що сказали інші, я наведу короткий приклад.

Якщо у вас 1D масив ...

>>> 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))

Де фліпуд - для 1d arra

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.