it-swarm.com.de

rufen Sie aus der Liste der Ganzzahlen die Nummer ab, die einem bestimmten Wert am nächsten ist

Angesichts einer Liste von Ganzzahlen möchte ich herausfinden, welche Zahl einer eingegebenen Zahl am nächsten kommt:

>>> myList = [4, 1, 88, 44, 3]
>>> myNumber = 5
>>> takeClosest(myList, myNumber)
...
4

Gibt es einen schnellen Weg, dies zu tun?

124
Ricky Robinson

Wenn wir nicht sicher sind, ob die Liste sortiert ist, können Sie die Funktion integrierte Funktion min() verwenden, um das Element zu finden, das den Mindestabstand von der angegebenen Anzahl hat.

>>> min(myList, key=lambda x:abs(x-myNumber))
4

Beachten Sie, dass es auch mit Diktaten mit int-Schlüsseln wie {1: "a", 2: "b"} funktioniert. Diese Methode benötigt O(n) Zeit.


Wenn die Liste bereits sortiert ist oder Sie den Preis für die Sortierung des Arrays nur einmal zahlen können, verwenden Sie die in @ Lauritz's answer dargestellte Bisektionsmethode Liste ist bereits sortiert, ist O(n) und die Sortierung ist O (n log n).)

261
kennytm

Wenn Sie "schnell ausführen" und nicht "schnell schreiben" meinen, sollte minnot Ihre Waffe der Wahl sein, außer in einem sehr engen Anwendungsfall. Die min-Lösung muss jede Zahl in der Liste prüfen. Und eine Berechnung für jede Zahl durchführen. Die Verwendung von bisect.bisect_left ist jedoch fast immer schneller.

Das "fast" kommt von der Tatsache, dass bisect_left eine Sortierung der Liste erfordert. Hoffentlich ist Ihr Anwendungsfall so, dass Sie die Liste einmal sortieren und dann in Ruhe lassen können. Auch wenn nicht, solange Sie nicht bei jedem Aufruf von takeClosest sortieren müssen, wird das Modul bisect wahrscheinlich ganz oben stehen. Wenn Sie sich nicht sicher sind, probieren Sie beide aus und schauen Sie sich die Unterschiede in der Realität an.

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.
    """
    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 funktioniert, indem Sie wiederholt eine Liste halbieren und herausfinden, in welcher Hälfte myNumber sich der mittlere Wert befindet. Das heißt, es hat eine Laufzeit von O (log n) im Gegensatz zu der O(n) Laufzeit der höchstwahrscheinlichen Antwort . Wenn wir die beiden Methoden vergleichen und beide mit einer sortierten myList versehen, sind dies die Ergebnisse:

 $ python -m timeit -s "
 vom nächstgelegenen Import takeClosest 
 vom Zufallsimport randint 
 a = range (-1000, 1000, 10)" "takeClosest (a, randint (-1100, 1100)) "

 100000 Schleifen, am besten von 3: 2,22 usec pro Schleife 

 $ Python -m timeit -s" 
 Vom nächsten Import mit_min 
 Vom zufälligen Importrandint
 a = Bereich (-1000, 1000, 10) "" mit_min (a, randint (-1100, 1100)) "

 10000 Schleifen, am besten 3: 43,9 usec pro Schleife 

In diesem Test ist bisect fast 20-mal schneller. Bei längeren Listen ist der Unterschied größer.

Was ist, wenn wir das Spielfeld nivellieren, indem wir die Voraussetzung entfernen, dass myList sortiert werden muss? Nehmen wir an, wir sortieren eine Kopie der Liste jedes MaltakeClosest, während die Lösung min unverändert bleibt. Unter Verwendung der Liste mit 200 Elementen im obigen Test ist die bisect-Lösung immer noch die schnellste, allerdings nur um etwa 30%.

Dies ist ein merkwürdiges Ergebnis, wenn man bedenkt, dass der Sortierschritt O (n log (n)) ist! Der einzige Grund, warum min immer noch verloren geht, besteht darin, dass die Sortierung in hochoptimiertem c-Code erfolgt, während min mit dem Aufruf einer Lambda-Funktion für jedes Element plotten muss. Wenn myList an Größe zunimmt, wird die min-Lösung mit der Zeit schneller. Beachten Sie, dass wir alles zu seinen Gunsten stapeln mussten, damit die min-Lösung gewinnt.

118
>>> takeClosest = lambda num,collection:min(collection,key=lambda x:abs(x-num))
>>> takeClosest(5,[4,1,88,44,3])
4

Ein Lambda ist eine spezielle Art, eine "anonyme" Funktion zu schreiben (eine Funktion, die keinen Namen hat). Sie können ihm einen beliebigen Namen zuweisen, da ein Lambda ein Ausdruck ist.

Die "lange" Schreibweise der oben genannten wäre:

def takeClosest(num,collection):
   return min(collection,key=lambda x:abs(x-num))
8
Burhan Khalid
def closest(list, Number):
    aux = []
    for valor in list:
        aux.append(abs(Number-valor))

    return aux.index(min(aux))

Dieser Code gibt Ihnen den Index der nächsten Nummer der Nummer in der Liste.

Die Lösung von KennyTM ist insgesamt die beste Lösung, aber in den Fällen, in denen Sie sie nicht verwenden können (wie Brython), erledigt diese Funktion die Arbeit

6
Gustavo Lima

Durchlaufen Sie die Liste und vergleichen Sie die aktuellste Zahl mit 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
3
João Silva

Es ist wichtig anzumerken, dass Lauritz 'Vorschlag, die Verwendung von Bisect zu verwenden, nicht wirklich den nächsten Wert in MyList zu MyNumber findet. Stattdessen findet bisect den nächsten Wert in order nach MyNumber in MyList. Im Falle von OP würden Sie also die Position 44 anstelle der Position 4 zurückgeben.

>>> myList = [1, 3, 4, 44, 88] 
>>> myNumber = 5
>>> pos = (bisect_left(myList, myNumber))
>>> myList[pos]
...
44

Um den Wert zu erhalten, der dem Wert 5 am nächsten kommt, können Sie die Liste in ein Array konvertieren und argmin von numpy verwenden.

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

Ich weiß nicht, wie schnell dies sein würde, meine Vermutung wäre "nicht sehr".

1
jmdeamer

Wenn ich zu @ Lauritz Antwort hinzufügen darf

Vergessen Sie nicht, vor der bisect_left-Zeile eine Bedingung hinzuzufügen,

if (myNumber > myList[-1] or myNumber < myList[0]):
    return False

so sieht der vollständige Code aus:

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
0
umn

Auf die Antwort von Gustavo Lima eingehen. Dasselbe kann man tun, ohne eine völlig neue Liste zu erstellen. Die Werte in der Liste können im Verlauf der FOR-Schleife durch die Differentiale ersetzt werden.

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.
0
JayJay123