tamsayılar listesinden belirli bir değere en yakın sayıyı al


158

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ı?


2
bunun listede olduğu dizini döndürmeye ne dersiniz?
Charlie Parker


1
@ sancho.s İyi gördüm. Her ne kadar bu sorunun cevapları diğer sorudaki cevaplardan çok daha iyi. Bu yüzden diğerini bunun kopyası olarak kapatmak için oy kullanacağım.
Jean-François Corbett

Yanıtlar:


326

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


13
Karmaşıklıktan bahsetmişken, bu, O(n)biraz hacklemenin bisectsize büyük bir gelişme sağlayacağı yerdir O(log n)(giriş diziniz sıralanırsa).
mic_e

5
@mic_e: Bu sadece Lauritz'in cevabı .
kennytm

3
bunun listede olduğu dizini döndürmeye ne dersiniz?
Charlie Parker

@CharlieParker Kendi uygulamanızı oluşturun min, items()liste yerine sözlük ( ) üzerinden çalıştırın ve sonunda değer yerine anahtarı döndürün .
Dustin Oprea

2
Veya kullanmak numpy.argminyerine minvalue yerine dizini alır.

148

İş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 .


2
Kendini sıralamak için O (N log N) gerekir, bu nedenle N büyüdüğünde daha yavaş olacaktır. Örneğin, kullanırsanız bunun daha yavaş olacağını a=range(-1000,1000,2);random.shuffle(a)göreceksiniz takeClosest(sorted(a), b).
kennytm

3
@KennyTM Bunu size söyleyeceğim ve cevabımda belirteceğim. Ancak, 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.
Lauritz V.Thaulow

bunun listede olduğu dizini döndürmeye ne dersiniz?
Charlie Parker

Eğer myListzaten bir olduğunu np.arraykullanılarak ardından np.searchsortedyerinde arasında bisecthızlıdır.
Michael Hall

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

2
Bununla birlikte, lambdaların isimlere atanmasının PEP 8'e göre cesaretinin kırıldığını unutmayın .
Evert Heylen

6
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


5

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

1
ayrıca dizini döndürebilirsiniz.
Charlie Parker

1
! Yanlış! Olmalı if abs(myList[i] - myNumber) < abs(closest - myNumber): closest = myList[i];. Bu değeri önceden saklamak daha iyi.
lk_vc

Elbette olduğu gibi işlev zaten en yakın olanın dizinini döndürür. OP'nin gereksinimlerini karşılamak için son satır en yakın okunmamalıdır = myList [i]
Paula Livingstone

2

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.


2
Lauritz'in işlevi doğru çalışıyor. Sadece bisect_left kullanıyorsunuz ama Lauritz ek kontrol yapan takeClosest (...) fonksiyonunu önerdi.
Kanat

NumPy kullanacaksanız, np.searchsortedyerine kullanabilirsiniz bisect_left. Ve @Kanat haklı - Lauritz çözümü yapar iki adayın daha yakın kazma kodunu dahil.
John Y

1

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.

1

@ 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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.