Quiero exponer la respuesta simple con varias notas de rendimiento. np.linalg.norm hará quizás más de lo que necesita:
dist = numpy.linalg.norm(a-b)
En primer lugar, esta función está diseñada para trabajar sobre una lista y devolver todos los valores, por ejemplo, para comparar la distancia desde pAel conjunto de puntos sP:
sP = set(points)
pA = point
distances = np.linalg.norm(sP - pA, ord=2, axis=1.) # 'distances' is a list
Recuerda varias cosas:
- Las llamadas a funciones de Python son caras.
- [Regular] Python no almacena en caché las búsquedas de nombres.
Entonces
def distance(pointA, pointB):
dist = np.linalg.norm(pointA - pointB)
return dist
No es tan inocente como parece.
>>> 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
En primer lugar, cada vez que lo llamamos, tenemos que hacer una búsqueda global de "np", una búsqueda de alcance para "linalg" y una búsqueda de alcance para "norma", y la sobrecarga de simplemente llamar la función puede equivaler a docenas de python instrucciones.
Por último, desperdiciamos dos operaciones para almacenar el resultado y volver a cargarlo para devolverlo ...
Primer paso en la mejora: agilice la búsqueda, salte la tienda
def distance(pointA, pointB, _norm=np.linalg.norm):
return _norm(pointA - pointB)
Obtenemos el más simplificado:
>>> 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
Sin embargo, la sobrecarga de llamadas a funciones todavía equivale a algo de trabajo. Y querrás hacer puntos de referencia para determinar si podrías ser mejor haciendo los cálculos tú mismo:
def distance(pointA, pointB):
return (
((pointA.x - pointB.x) ** 2) +
((pointA.y - pointB.y) ** 2) +
((pointA.z - pointB.z) ** 2)
) ** 0.5 # fast sqrt
En algunas plataformas, **0.5es más rápido quemath.sqrt . Su experiencia puede ser diferente.
**** Notas de rendimiento avanzadas.
¿Por qué estás calculando la distancia? Si el único propósito es mostrarlo,
print("The target is %.2fm away" % (distance(a, b)))
superar. Pero si está comparando distancias, haciendo verificaciones de rango, etc., me gustaría agregar algunas observaciones útiles de rendimiento.
Tomemos dos casos: ordenar por distancia o seleccionar una lista de elementos que cumplan con una restricción de rango.
# 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)
Lo primero que debemos recordar es que estamos usando Pitágoras para calcular la distancia ( dist = sqrt(x^2 + y^2 + z^2)), por lo que estamos haciendo muchas sqrtllamadas. Matemáticas 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
En resumen: hasta que realmente necesitemos la distancia en una unidad de X en lugar de X ^ 2, podemos eliminar la parte más difícil de los cálculos.
# 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)
Genial, ambas funciones ya no tienen raíces cuadradas caras. Eso será mucho más rápido. También podemos mejorar in_range convirtiéndolo en un generador:
def in_range(origin, range, things):
range_sq = range**2
yield from (thing for thing in things
if distance_sq(origin, thing) <= range_sq)
Esto tiene beneficios especialmente si está haciendo algo como:
if any(in_range(origin, max_dist, things)):
...
Pero si lo siguiente que vas a hacer requiere una distancia,
for nearby in in_range(origin, walking_distance, hotdog_stands):
print("%s %.2fm" % (nearby.name, distance(origin, nearby)))
considere ceder tuplas:
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)
Esto puede ser especialmente útil si puede encadenar verificaciones de rango ('encontrar cosas que están cerca de X y dentro de Nm de Y', ya que no tiene que calcular la distancia nuevamente).
Pero, ¿qué pasa si estamos buscando una lista realmente grande thingsy anticipamos que muchos de ellos no merecen ser considerados?
En realidad, hay una optimización muy simple:
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
Si esto es útil dependerá del tamaño de las "cosas".
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 ...
Y nuevamente, considere obtener el dist_sq. Nuestro ejemplo de hot dog se convierte en:
# 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))