Bei einer Liste von ganzen Zahlen möchte ich herausfinden, welche Zahl einer Zahl, die ich bei der Eingabe gebe, am nächsten kommt:
>>> myList = [4, 1, 88, 44, 3]
>>> myNumber = 5
>>> takeClosest(myList, myNumber)
...
4
Gibt es eine schnelle Möglichkeit, dies zu tun?
Antworten:
Wenn wir nicht sicher sind, ob die Liste sortiert ist, können wir die integrierte
min()
Funktion verwenden , um das Element zu finden, das den Mindestabstand von der angegebenen Anzahl hat.Beachten Sie, dass es auch mit Diktaten mit int-Tasten funktioniert, wie z
{1: "a", 2: "b"}
. Diese Methode benötigt O (n) Zeit.Wenn die Liste bereits sortiert ist oder Sie den Preis für das Sortieren des Arrays nur einmal bezahlen können, verwenden Sie die in @ Lauritz 'Antwort dargestellte Halbierungsmethode, die nur O (log n) Zeit benötigt (beachten Sie jedoch, dass die Überprüfung, ob eine Liste bereits sortiert ist, O ist (n) und Sortieren ist O (n log n).)
quelle
O(n)
, an dem Sie durch ein wenig Hackenbisect
eine massive Verbesserung erzielenO(log n)
(wenn Ihr Eingabearray sortiert ist).min
, führen Sie sie über ein Dictionary (items()
) anstelle einer Liste aus und geben Sie am Ende den Schlüssel anstelle des Werts zurück.numpy.argmin
anstelle vonmin
, um den Index anstelle des Werts abzurufen.Ich werde die Funktion umbenennen
take_closest
, um sie den PEP8-Namenskonventionen anzupassen.Wenn Sie schnell ausführen und nicht schnell schreiben möchten,
min
sollte dies nicht die Waffe Ihrer Wahl sein, außer in einem sehr engen Anwendungsfall. Diemin
Lösung muss jede Zahl in der Liste untersuchen und für jede Zahl eine Berechnung durchführen. Mitbisect.bisect_left
stattdessen ist fast immer schneller.Das "fast" kommt von der Tatsache,
bisect_left
dass die Liste sortiert werden muss, um zu funktionieren. Hoffentlich ist Ihr Anwendungsfall so, dass Sie die Liste einmal sortieren und dann in Ruhe lassen können. Selbst wenn nicht, solange Sie nicht vor jedem Anruf sortieren müssentake_closest
, wird dasbisect
Modul wahrscheinlich die Nase vorn haben. Wenn Sie Zweifel haben, versuchen Sie beides und sehen Sie sich den Unterschied in der realen Welt an.Bisect halbiert wiederholt eine Liste und ermittelt
myNumber
anhand des Mittelwerts , in welcher Hälfte sich die Hälfte befinden muss. Dies bedeutet, dass es eine Laufzeit von O (log n) hat, im Gegensatz zur Laufzeit von O (n) der Antwort mit der höchsten Abstimmung . Wenn wir die beiden Methoden vergleichen und beide sortiert liefernmyList
, sind dies die Ergebnisse:Also in diesem speziellen Test
bisect
ist fast 20 mal schneller. Bei längeren Listen ist der Unterschied größer.Was ist, wenn wir das Spielfeld ausgleichen, indem wir die
myList
zu sortierende Voraussetzung entfernen ? Nehmen wir an, wir sortieren bei jedemtake_closest
Aufruf eine Kopie der Liste , während diemin
Lösung unverändert bleibt . Unter Verwendung der 200-Punkte-Liste im obigen Test ist diebisect
Lösung immer noch die schnellste, wenn auch nur um etwa 30%.Dies ist ein seltsames Ergebnis, wenn man bedenkt, dass der Sortierschritt O (n log (n)) ist ! Der einzige Grund, der
min
immer noch verloren geht, ist, dass die Sortierungmin
in hochoptimiertem c-Code erfolgt, während für jedes Element eine Lambda-Funktion aufgerufen werden muss. MitmyList
zunehmender Größe wird diemin
Lösung möglicherweise schneller. Beachten Sie, dass wir alles zu seinen Gunsten stapeln mussten, damit diemin
Lösung gewinnt.quelle
a=range(-1000,1000,2);random.shuffle(a)
feststellen, dasstakeClosest(sorted(a), b)
dies langsamer wird.getClosest
jedoch für jede Sorte mehr als einmal aufgerufen werden kann, ist dies schneller, und für den Anwendungsfall "Einmal sortieren" ist dies ein Kinderspiel.myList
ist schon einenp.array
dann Verwendungnp.searchsorted
anstelle vonbisect
ist schneller.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" Art, das Obige zu schreiben, wäre:
quelle