Çeşitli performans notları ile basit cevaba açıklamak istiyorum. np.linalg.norm belki de ihtiyacınız olandan daha fazlasını yapar:
dist = numpy.linalg.norm(a-b)
İlk olarak - bu işlev bir liste üzerinde çalışmak ve tüm değerleri döndürmek için tasarlanmıştır, örneğin pA
nokta kümesiyle arasındaki mesafeyi karşılaştırmak için sP
:
sP = set(points)
pA = point
distances = np.linalg.norm(sP - pA, ord=2, axis=1.) # 'distances' is a list
Birkaç şeyi hatırlayın:
- Python işlev çağrıları pahalıdır.
- [Normal] Python ad aramalarını önbelleğe almaz.
Yani
def distance(pointA, pointB):
dist = np.linalg.norm(pointA - pointB)
return dist
göründüğü kadar masum değil.
>>> 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
İlk olarak - her dediğimizde, "np" için küresel bir arama, "linalg" için kapsamlı bir arama ve "norm" için kapsamlı bir arama yapmalıyız ve sadece işlevi çağırmanın yükü onlarca python'a eşit olabilir Talimatlar.
Son olarak, sonucu saklamak ve iade için yeniden yüklemek için iki işlem harcadık ...
İyileştirmede ilk geçiş: aramayı daha hızlı yapın, mağazayı atlayın
def distance(pointA, pointB, _norm=np.linalg.norm):
return _norm(pointA - pointB)
Çok daha akıcı hale getirdik:
>>> 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
Yine de, işlev çağrısı ek yükü hala bir iş anlamına gelir. Ve matematiği kendiniz daha iyi yapıp yapamayacağınızı belirlemek için kriterler yapmak istersiniz:
def distance(pointA, pointB):
return (
((pointA.x - pointB.x) ** 2) +
((pointA.y - pointB.y) ** 2) +
((pointA.z - pointB.z) ** 2)
) ** 0.5 # fast sqrt
Bazı platformlarda, **0.5
daha hızlıdır math.sqrt
. Kilometreniz değişebilir.
**** Gelişmiş performans notları.
Neden mesafeyi hesaplıyorsunuz? Tek amaç bunu göstermekse,
print("The target is %.2fm away" % (distance(a, b)))
hareket etmek. Ancak mesafeleri karşılaştırıyorsanız, aralık kontrolleri vb. Yapıyorsanız, bazı yararlı performans gözlemleri eklemek istiyorum.
İki durumu ele alalım: mesafeye göre sıralama veya bir menzil sınırlamasını karşılayan öğelere bir listeyi kaldırma.
# 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)
Hatırlamamız gereken ilk şey , mesafeyi ( ) hesaplamak için Pisagor kullandığımızdır, bu dist = sqrt(x^2 + y^2 + z^2)
yüzden çok fazla sqrt
çağrı yapıyoruz . Matematik 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
Kısacası, aslında mesafeyi X ^ 2 yerine X biriminde gerektirene kadar, hesaplamaların en zor kısmını ortadan kaldırabiliriz.
# 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)
Harika, her iki işlev de artık pahalı kare kökler yapmıyor. Çok daha hızlı olacak. Ayrıca in_range öğesini bir jeneratöre dönüştürerek de geliştirebiliriz:
def in_range(origin, range, things):
range_sq = range**2
yield from (thing for thing in things
if distance_sq(origin, thing) <= range_sq)
Özellikle aşağıdaki gibi bir şey yapıyorsanız bunun faydaları vardır:
if any(in_range(origin, max_dist, things)):
...
Ama yapacağınız bir sonraki şey bir mesafe gerektiriyorsa,
for nearby in in_range(origin, walking_distance, hotdog_stands):
print("%s %.2fm" % (nearby.name, distance(origin, nearby)))
tuples vermeyi düşünün:
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)
Bu, özellikle aralık kontrollerini zincirleyebiliyorsanız ('X'in yakınında ve Y'nin Nm'si içinde olan şeyleri bul') yararlı olabilir, çünkü mesafeyi tekrar hesaplamanız gerekmez.
Peki, gerçekten büyük bir liste arıyoruz things
ve birçoğunun dikkate almaya değer olmadığını tahmin edersek ne olur ?
Aslında çok basit bir optimizasyon var:
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
Bunun faydalı olup olmadığı 'şeylerin' boyutuna bağlı olacaktır.
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 ...
Ve yine, dist_sq vermeyi düşünün. Hotdog örneğimiz daha sonra:
# 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))