Эффективный способ получить индекс минимального значения в длинном векторе, python

#python #list #indexing #latitude-longitude #minimum

#python #Список #индексирование #широта-долгота #минимальный

Вопрос:

У меня есть длинный список значений долготы (len (Lon) = 420481) и еще один список значений широты. Я хочу найти соответствующую широту минимальной долготе.

Я пытался:

 SE_Lat = [Lat[x] for x,y in enumerate(Lon) if y == min(Lon)]
  

но для завершения требуется время.

Кто-нибудь знает более эффективный способ?

Возможно, у вас также есть предложения по этому поводу: теперь я пытаюсь найти ближайшую соответствующую широту к новой долготе, которой нет в исходном векторе долготы. Я попробовал это:

 minDiff = [min(abs(x - lon_new) for x in lons)] # not very quick, but works
[(lat,lon) for lat,lon in izip(lats,lons) if abs(lon-lon_new)==minDiff]
  

Последняя строка выдает ошибку, потому что есть несколько совпадений. На данный момент я не знаю, как найти только одно значение, скажем, первое. Любая помощь приветствуется!

Ответ №1:

Могу ли я порекомендовать numpy?

 import numpy
nplats = numpy.array(lats)
nplons = numpy.array(lons)

# this part is 20x faster than using the built-in python functions
index = numpy.argmin(nplats)

print nplats[index], nplons[index]
  

это намного быстрее, чем решение min (izip ()) (~ 20 раз с использованием моей настройки при использовании 420481 случайно созданных записей), хотя, конечно, вам нужно было бы сохранить значения ваших данных в numpy, чтобы воспользоваться преимуществами этого ускорения.

Ответ №2:

 min(itertools.izip(Lat, Lon), key=operator.itemgetter(1))[0]
  

Комментарии:

1. импорт lazy-zip в itertools вообще необходим, поскольку поиск min обязательно должен касаться каждого отдельного элемента и, таким образом, будет расширять каждый элемент в итераторе (кроме того, в python3 zip по умолчанию является ленивым)

2. Это все еще много элементов, и генерация списка в первую очередь будет медленной.

3. Это не проблема в python3, но после тестирования вы правы для python2. 1 =) — для справки, просто сделайте x=min(zip(range(10**6))) как с zip, так и с izip в python и python3; zip выполняется быстро в python3, izip так же быстро в python2 и zip очень медленно в python2.

Ответ №3:

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

 SE_Lat = [Lat[x] for x,y in enumerate(Lon) if y == min(Lon)]
  

Мы знаем из OP, что len(Lon) == 420481 . Теперь поиск минимального значения — это операция O (N) (вы должны просмотреть каждое значение хотя бы один раз). При понимании списка условие пересматривается на каждой итерации. Приведенный выше код пересчитывает минимальное значение при каждом проходе через цикл, превращая то, что должно быть операцией O (N), в O (N ^ 2) (в данном случае всего 177 миллиардов итераций).

Простое кэширование результата min(Lon) в локальной переменной и использование его в условии цикла вместо его повторного вычисления на каждой итерации, вероятно, снизит время выполнения до приемлемого уровня.

Однако, способ, которым я лично поступил бы по этому поводу (предполагая, что я хотел бы получить всю широту, долготу и индекс позже):

 min_longitude, min_index = min(longitude, index for index, longitude in enumerate(Lon))
min_latitude = Lat[min_index]
  

Однако существует множество возможностей, и какая из них лучше, зависит от конкретного варианта использования.

Ответ №4:

Просто сначала найдите индекс:

 index = min(enumerate(Lon), key=operator.itemgetter(1))[1] 
Lat[index]
  

Комментарии:

1. Вы уверены в финале [1] ? Я считаю, что так и должно быть [0] , поскольку это тот индекс, который вам нужен.

Ответ №5:

 pairs = zip(latitudes, longitudes)
minLonPair = min(pairs, key=lambda p:p[1])
print(minLonPair[0])
  

Согласно решению Игнасио, если вы используете python2, вы захотите использовать izip вместо zip . Это, однако, верно для всего, что вы делаете в python2.

Ответ №6:

Вот мой первоначальный ответ:

 >>> lats = [1,2,3,4]
>>> lons = [5,4,8,9]
>>> from itertools import izip
>>> min(izip(lats,lons), key=lambda x:x[1])
(2, 4)
  

Но я вижу, что OP, похоже, допускает наличие нескольких совпадений при минимальном значении lon, и для этого, я не думаю, что существует однострочник. Хитрость в том, что вы хотите найти min (lons) только один раз, а не по одному разу для каждой пары lat, lon:

 >>> lats = [1,2,3,4]
>>> lons = [5,4,8,4]
>>> minlon = min(lons)
>>> [(lat,lon) for lat,lon in izip(lats,lons) if lon==minlon]
[(2, 4), (4, 4)]
  

Этот однострочный вариант может вам подойти, поскольку лямбда-аргумент minlon следует вычислять только один раз:

 >>> filter(lambda latlon,minlon=min(lons):latlon[1]==minlon, izip(lats,lons))
[(2, 4), (4, 4)]
  

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

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

 from itertools import izip

def get_lats_at_min_lon(lats, lons):
    minlon = 200
    minlats = []
    for lat,lon in izip(lats, lons):
        if lon < minlon:
            minlats = [lat]
            minlon = lon
        elif lon == minlon:
            minlats.append(lat)
    return minlon, minlats

lats = iter([1,2,3,4])
lons = iter([5,4,8,4])

print get_lats_at_min_lon(lats,lons)
  

С принтами:

 (4, [2, 4])
  

Комментарии:

1. Спасибо за все ответы, ребята! Практически все, что вы предложили, сработало хорошо и быстро. Я использовал однострочный с фильтром, который отлично работает.