Tamsayıların bir listesi verildiğinde, hangi sayının girişte verdiğim bir sayıya en yakın olduğunu bulmak istiyorum:
>>> myList = [4, 1, 88, 44, 3]
>>> myNumber = 5
>>> takeClosest(myList, myNumber)
...
4
Bunu yapmanın hızlı bir yolu var mı?
Tamsayıların bir listesi verildiğinde, hangi sayının girişte verdiğim bir sayıya en yakın olduğunu bulmak istiyorum:
>>> myList = [4, 1, 88, 44, 3]
>>> myNumber = 5
>>> takeClosest(myList, myNumber)
...
4
Bunu yapmanın hızlı bir yolu var mı?
Yanıtlar:
Listenin sıralandığından emin değilseniz , belirtilen sayıdan minimum mesafeye sahip öğeyi bulmak için yerleşik min()
işlevi kullanabiliriz .
>>> min(myList, key=lambda x:abs(x-myNumber))
4
Ayrıca int tuşlarına sahip diktlerle de çalıştığını unutmayın {1: "a", 2: "b"}
. Bu yöntem O (n) zaman alır.
Liste zaten sıralandıysa veya diziyi yalnızca bir kez sıralamanın fiyatını ödeyebiliyorsanız, @ Lauritz'in yanıtında yalnızca O (oturum n) zamanını alan iki yönlü yöntemi kullanın (ancak bir listenin zaten sıralanıp sıralanmadığını kontrol edin Not (n) ve sıralama O (n log n) 'dir.)
O(n)
biraz hacklemenin bisect
size büyük bir gelişme sağlayacağı yerdir O(log n)
(giriş diziniz sıralanırsa).
min
, items()
liste yerine sözlük ( ) üzerinden çalıştırın ve sonunda değer yerine anahtarı döndürün .
İşlevi yeniden adlandıracağım take_closest
PEP8 adlandırma kurallarına uyacak şekilde yeniden adlandıracağım.
Eğer demek istediğin, çabuk yürütmek, çabuk yazma aksine, min
gerektiği değil bir çok dar kullanım durumu hariç, seçtiğiniz silah olun. min
Çözelti listedeki her numara incelemek gerekiyor ve her sayı için bir hesaplama yapmak. bisect.bisect_left
Bunun yerine kullanmak neredeyse her zaman daha hızlıdır.
"Neredeyse" bisect_left
listenin çalışması için sıralanmasını gerektiren gerçeğinden gelir . Umarım, kullanım durumunuz listeyi bir kez sıralayabileceğiniz ve daha sonra yalnız bırakabileceğiniz şekildedir. Olmasa bile, her aramadan önce sıralamanız gerekmediği sürece take_closest
, bisect
modül muhtemelen en üstte çıkacaktır. Şüpheniz varsa, ikisini de deneyin ve gerçek dünya farkına bakın.
from bisect import bisect_left
def take_closest(myList, myNumber):
"""
Assumes myList is sorted. Returns closest value to myNumber.
If two numbers are equally close, return the smallest number.
"""
pos = bisect_left(myList, myNumber)
if pos == 0:
return myList[0]
if pos == len(myList):
return myList[-1]
before = myList[pos - 1]
after = myList[pos]
if after - myNumber < myNumber - before:
return after
else:
return before
Bisect, bir listeyi tekrar tekrar yarıya indirip myNumber
orta değere bakarak hangi yarının olması gerektiğini bularak çalışır . Bu , en yüksek oyu alan cevabın O (n) çalışma süresinin aksine O (log n) çalışma süresine sahip olduğu anlamına gelir . İki yöntemi karşılaştırır ve her ikisini de sıralı olarak sağlarsak , sonuçlar şunlardır:myList
$ python -m timeit -s " en yakın ithalattan take_closest rastgele ithalat randintinden a = aralık (-1000, 1000, 10) "" take_closest (a, randint (-1100, 1100)) " 100000 döngü, döngü başına en iyi 3: 2,22 usec $ python -m timeit -s " en yakın içe aktarmadan with_min rastgele ithalat randintinden a = aralık (-1000, 1000, 10) "" with_min (a, randint (-1100, 1100)) " 10000 döngü, döngü başına en iyi 3: 43,9 usec
Yani bu özel testte, bisect
neredeyse 20 kat daha hızlıdır. Daha uzun listeler için fark daha büyük olacaktır.
myList
Sıralanması gereken ön koşulu kaldırarak oyun alanını düzleştirirsek ne olur ? Diyelim ki, her take_closest
çağrıldığında listenin bir kopyasını min
sıralarken çözümü değiştirmeden bırakıyoruz . Yukarıdaki testte 200 maddelik liste kullanılarak, bisect
çözüm en hızlıdır, ancak sadece yaklaşık% 30'dur.
Bu, sıralama adımının O (n log (n)) olduğu düşünüldüğünde garip bir sonuçtur ! min
Hala kaybetmek tek sıralama, min
her öğe için bir lambda fonksiyonu çağırmak boyunca çizmek gerekirken , sıralama son derece optimize c kodu yapılır olmasıdır . Gibi myList
boyut olarak büyüdükçe, min
çözüm nihayetinde daha hızlı olacaktır. min
Çözümün kazanması için her şeyi kendi lehine toplamak zorunda olduğumuzu unutmayın .
a=range(-1000,1000,2);random.shuffle(a)
göreceksiniz takeClosest(sorted(a), b)
.
getClosest
her tür için bir kereden fazla çağrılabildiği sürece , bu daha hızlı olacaktır ve bir kez sıralanan kullanım durumunda, bu bir beyinsizdir.
myList
zaten bir olduğunu np.array
kullanılarak ardından np.searchsorted
yerinde arasında bisect
hızlıdır.
>>> takeClosest = lambda num,collection:min(collection,key=lambda x:abs(x-num))
>>> takeClosest(5,[4,1,88,44,3])
4
Bir lambda bir "anonim" işlevini (bir adı olmayan bir fonksiyonu) yazma özel bir yoludur. İstediğiniz herhangi bir adı atayabilirsiniz çünkü lambda bir ifadedir.
Yukarıdakileri yazmanın "uzun" yolu:
def takeClosest(num,collection):
return min(collection,key=lambda x:abs(x-num))
def closest(list, Number):
aux = []
for valor in list:
aux.append(abs(Number-valor))
return aux.index(min(aux))
Bu kod, size listedeki en yakın Sayı dizini verir.
KennyTM tarafından verilen çözüm genel olarak en iyisidir, ancak kullanamayacağınız durumlarda (brython gibi), bu işlev işi yapacak
Listeyi yineleyin ve mevcut en yakın sayıyı şununla karşılaştırın abs(currentNumber - myNumber)
:
def takeClosest(myList, myNumber):
closest = myList[0]
for i in range(1, len(myList)):
if abs(i - myNumber) < closest:
closest = i
return closest
if abs(myList[i] - myNumber) < abs(closest - myNumber): closest = myList[i];
. Bu değeri önceden saklamak daha iyi.
Lauritz'in bisect kullanma önerisinin aslında MyList'te MyNumber'a en yakın değeri bulamadığını belirtmek önemlidir. Bunun yerine bisect, MyList'te MyNumber'dan sonraki sırayı bulur . Yani OP durumunda 4 yerine 4 pozisyonunu döndürürsünüz.
>>> myList = [1, 3, 4, 44, 88]
>>> myNumber = 5
>>> pos = (bisect_left(myList, myNumber))
>>> myList[pos]
...
44
5'e en yakın değeri elde etmek için listeyi bir diziye dönüştürmeyi ve bu şekilde numpy'den argmin kullanmayı deneyebilirsiniz.
>>> import numpy as np
>>> myNumber = 5
>>> myList = [1, 3, 4, 44, 88]
>>> myArray = np.array(myList)
>>> pos = (np.abs(myArray-myNumber)).argmin()
>>> myArray[pos]
...
4
Bunun ne kadar hızlı olacağını bilmiyorum, tahminim "çok değil" olurdu.
np.searchsorted
yerine kullanabilirsiniz bisect_left
. Ve @Kanat haklı - Lauritz çözümü yapar iki adayın daha yakın kazma kodunu dahil.
Gustavo Lima'nın cevabına genişleyen. Aynı şey tamamen yeni bir liste oluşturmadan da yapılabilir. FOR
Döngü ilerledikçe listedeki değerler diferansiyel ile değiştirilebilir .
def f_ClosestVal(v_List, v_Number):
"""Takes an unsorted LIST of INTs and RETURNS INDEX of value closest to an INT"""
for _index, i in enumerate(v_List):
v_List[_index] = abs(v_Number - i)
return v_List.index(min(v_List))
myList = [1, 88, 44, 4, 4, -2, 3]
v_Num = 5
print(f_ClosestVal(myList, v_Num)) ## Gives "3," the index of the first "4" in the list.
@ Lauritz'in cevabına ekleyebilirsem
Bir çalışma hatası olmamak için bisect_left
satırdan önce bir koşul eklemeyi unutmayın :
if (myNumber > myList[-1] or myNumber < myList[0]):
return False
tam kod şöyle görünecektir:
from bisect import bisect_left
def takeClosest(myList, myNumber):
"""
Assumes myList is sorted. Returns closest value to myNumber.
If two numbers are equally close, return the smallest number.
If number is outside of min or max return False
"""
if (myNumber > myList[-1] or myNumber < myList[0]):
return False
pos = bisect_left(myList, myNumber)
if pos == 0:
return myList[0]
if pos == len(myList):
return myList[-1]
before = myList[pos - 1]
after = myList[pos]
if after - myNumber < myNumber - before:
return after
else:
return before