Ich möchte die einfache Antwort mit verschiedenen Leistungsnotizen erläutern. np.linalg.norm kann vielleicht mehr als Sie brauchen:
dist = numpy.linalg.norm(a-b)
Erstens: Diese Funktion dient zum Überarbeiten einer Liste und zum Zurückgeben aller Werte, z. B. zum Vergleichen der Entfernung von pA
der Punktmenge sP
:
sP = set(points)
pA = point
distances = np.linalg.norm(sP - pA, ord=2, axis=1.) # 'distances' is a list
Denken Sie an verschiedene Dinge:
- Python-Funktionsaufrufe sind teuer.
- [Normal] Python speichert keine Namenssuche zwischen.
Damit
def distance(pointA, pointB):
dist = np.linalg.norm(pointA - pointB)
return dist
ist nicht so unschuldig wie es aussieht.
>>> dis.dis(distance)
2 0 LOAD_GLOBAL 0 (np)
2 LOAD_ATTR 1 (linalg)
4 LOAD_ATTR 2 (norm)
6 LOAD_FAST 0 (pointA)
8 LOAD_FAST 1 (pointB)
10 BINARY_SUBTRACT
12 CALL_FUNCTION 1
14 STORE_FAST 2 (dist)
3 16 LOAD_FAST 2 (dist)
18 RETURN_VALUE
Erstens - jedes Mal, wenn wir es aufrufen, müssen wir eine globale Suche nach "np", eine Suche nach "linalg" und eine Suche nach "norm" durchführen, und der Aufwand für das bloße Aufrufen der Funktion kann Dutzenden von Python entsprechen Anleitung.
Zuletzt haben wir zwei Vorgänge verschwendet, um das Ergebnis zu speichern und für die Rückgabe neu zu laden ...
Erster Durchgang bei der Verbesserung: Beschleunigen Sie die Suche, überspringen Sie den Speicher
def distance(pointA, pointB, _norm=np.linalg.norm):
return _norm(pointA - pointB)
Wir werden umso schlanker:
>>> dis.dis(distance)
2 0 LOAD_FAST 2 (_norm)
2 LOAD_FAST 0 (pointA)
4 LOAD_FAST 1 (pointB)
6 BINARY_SUBTRACT
8 CALL_FUNCTION 1
10 RETURN_VALUE
Der Aufwand für Funktionsaufrufe beträgt jedoch noch einige Arbeit. Und Sie sollten Benchmarks durchführen, um festzustellen, ob Sie die Mathematik besser selbst durchführen können:
def distance(pointA, pointB):
return (
((pointA.x - pointB.x) ** 2) +
((pointA.y - pointB.y) ** 2) +
((pointA.z - pointB.z) ** 2)
) ** 0.5 # fast sqrt
Auf einigen Plattformen **0.5
ist schneller als math.sqrt
. Ihr Kilometerstand kann variieren.
**** Erweiterte Leistungshinweise.
Warum berechnen Sie die Entfernung? Wenn der einzige Zweck darin besteht, es anzuzeigen,
print("The target is %.2fm away" % (distance(a, b)))
weiter machen. Wenn Sie jedoch Entfernungen vergleichen, Entfernungsprüfungen durchführen usw., möchte ich einige nützliche Leistungsbeobachtungen hinzufügen.
Nehmen wir zwei Fälle: Sortieren nach Entfernung oder Auslesen einer Liste nach Elementen, die eine Bereichsbeschränkung erfüllen.
# Ultra naive implementations. Hold onto your hat.
def sort_things_by_distance(origin, things):
return things.sort(key=lambda thing: distance(origin, thing))
def in_range(origin, range, things):
things_in_range = []
for thing in things:
if distance(origin, thing) <= range:
things_in_range.append(thing)
Das erste, woran wir uns erinnern müssen, ist, dass wir Pythagoras verwenden , um die Entfernung ( dist = sqrt(x^2 + y^2 + z^2)
) zu berechnen, damit wir viele sqrt
Anrufe tätigen. Mathe 101:
dist = root ( x^2 + y^2 + z^2 )
:.
dist^2 = x^2 + y^2 + z^2
and
sq(N) < sq(M) iff M > N
and
sq(N) > sq(M) iff N > M
and
sq(N) = sq(M) iff N == M
Kurz gesagt: Bis wir tatsächlich den Abstand in einer Einheit von X anstelle von X ^ 2 benötigen, können wir den schwierigsten Teil der Berechnungen eliminieren.
# Still naive, but much faster.
def distance_sq(left, right):
""" Returns the square of the distance between left and right. """
return (
((left.x - right.x) ** 2) +
((left.y - right.y) ** 2) +
((left.z - right.z) ** 2)
)
def sort_things_by_distance(origin, things):
return things.sort(key=lambda thing: distance_sq(origin, thing))
def in_range(origin, range, things):
things_in_range = []
# Remember that sqrt(N)**2 == N, so if we square
# range, we don't need to root the distances.
range_sq = range**2
for thing in things:
if distance_sq(origin, thing) <= range_sq:
things_in_range.append(thing)
Großartig, beide Funktionen machen keine teuren Quadratwurzeln mehr. Das geht viel schneller. Wir können in_range auch verbessern, indem wir es in einen Generator konvertieren:
def in_range(origin, range, things):
range_sq = range**2
yield from (thing for thing in things
if distance_sq(origin, thing) <= range_sq)
Dies hat insbesondere Vorteile, wenn Sie Folgendes tun:
if any(in_range(origin, max_dist, things)):
...
Aber wenn das nächste, was Sie tun werden, eine Distanz erfordert,
for nearby in in_range(origin, walking_distance, hotdog_stands):
print("%s %.2fm" % (nearby.name, distance(origin, nearby)))
erwägen, Tupel zu ergeben:
def in_range_with_dist_sq(origin, range, things):
range_sq = range**2
for thing in things:
dist_sq = distance_sq(origin, thing)
if dist_sq <= range_sq: yield (thing, dist_sq)
Dies kann besonders nützlich sein, wenn Sie Entfernungsprüfungen verketten können ('Dinge finden, die sich in der Nähe von X und innerhalb von Nm von Y befinden', da Sie die Entfernung nicht erneut berechnen müssen).
Aber was ist, wenn wir eine wirklich große Liste von suchen things
und davon ausgehen, dass viele von ihnen nicht in Betracht gezogen werden sollten?
Es gibt tatsächlich eine sehr einfache Optimierung:
def in_range_all_the_things(origin, range, things):
range_sq = range**2
for thing in things:
dist_sq = (origin.x - thing.x) ** 2
if dist_sq <= range_sq:
dist_sq += (origin.y - thing.y) ** 2
if dist_sq <= range_sq:
dist_sq += (origin.z - thing.z) ** 2
if dist_sq <= range_sq:
yield thing
Ob dies nützlich ist, hängt von der Größe der 'Dinge' ab.
def in_range_all_the_things(origin, range, things):
range_sq = range**2
if len(things) >= 4096:
for thing in things:
dist_sq = (origin.x - thing.x) ** 2
if dist_sq <= range_sq:
dist_sq += (origin.y - thing.y) ** 2
if dist_sq <= range_sq:
dist_sq += (origin.z - thing.z) ** 2
if dist_sq <= range_sq:
yield thing
elif len(things) > 32:
for things in things:
dist_sq = (origin.x - thing.x) ** 2
if dist_sq <= range_sq:
dist_sq += (origin.y - thing.y) ** 2 + (origin.z - thing.z) ** 2
if dist_sq <= range_sq:
yield thing
else:
... just calculate distance and range-check it ...
Und noch einmal, erwägen Sie, dist_sq zu erhalten. Unser Hotdog-Beispiel lautet dann:
# Chaining generators
info = in_range_with_dist_sq(origin, walking_distance, hotdog_stands)
info = (stand, dist_sq**0.5 for stand, dist_sq in info)
for stand, dist in info:
print("%s %.2fm" % (stand, dist))