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 bisectsize 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, mingerektiğ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_leftBunun yerine kullanmak neredeyse her zaman daha hızlıdır.
"Neredeyse" bisect_leftlistenin ç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, bisectmodü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 myNumberorta 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, bisectneredeyse 20 kat daha hızlıdır. Daha uzun listeler için fark daha büyük olacaktır.
myListSı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ı minsı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 ! minHala kaybetmek tek sıralama, minher öğ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 myListboyut 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).
getClosesther 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.
myListzaten bir olduğunu np.arraykullanılarak ardından np.searchsortedyerinde arasında bisecthı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.searchsortedyerine 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. FORDö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_leftsatı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