Saya ingin menguraikan jawaban sederhana dengan berbagai catatan kinerja. np.linalg.norm mungkin akan melakukan lebih dari yang Anda butuhkan:
dist = numpy.linalg.norm(a-b)
Pertama - fungsi ini dirancang untuk mengerjakan daftar dan mengembalikan semua nilai, misalnya untuk membandingkan jarak dari pA
ke set poin sP
:
sP = set(points)
pA = point
distances = np.linalg.norm(sP - pA, ord=2, axis=1.) # 'distances' is a list
Ingat beberapa hal:
- Panggilan fungsi python mahal.
- [Biasa] Python tidak mencari nama cache.
Begitu
def distance(pointA, pointB):
dist = np.linalg.norm(pointA - pointB)
return dist
tidak sesugu kelihatannya.
>>> 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
Pertama - setiap kali kita menyebutnya, kita harus melakukan pencarian global untuk "np", pencarian lingkup untuk "linalg" dan pencarian lingkup untuk "norma", dan overhead hanya memanggil fungsi dapat menyamakan dengan puluhan python instruksi.
Terakhir, kami membuang dua operasi untuk menyimpan hasilnya dan memuatnya kembali ...
Pass pertama pada peningkatan: membuat pencarian lebih cepat, lewati toko
def distance(pointA, pointB, _norm=np.linalg.norm):
return _norm(pointA - pointB)
Kami mendapatkan yang lebih efisien:
>>> 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
Fungsi panggilan overhead masih sejumlah pekerjaan, meskipun. Dan Anda ingin melakukan tolok ukur untuk menentukan apakah Anda bisa melakukan sendiri matematika dengan lebih baik:
def distance(pointA, pointB):
return (
((pointA.x - pointB.x) ** 2) +
((pointA.y - pointB.y) ** 2) +
((pointA.z - pointB.z) ** 2)
) ** 0.5 # fast sqrt
Pada beberapa platform, **0.5
lebih cepat daripada math.sqrt
. Jarak tempuh Anda mungkin beragam.
**** Catatan kinerja lanjutan.
Mengapa Anda menghitung jarak? Jika satu-satunya tujuan adalah untuk menampilkannya,
print("The target is %.2fm away" % (distance(a, b)))
berjalan terus. Tetapi jika Anda membandingkan jarak, melakukan pemeriksaan jarak, dll., Saya ingin menambahkan beberapa pengamatan kinerja yang bermanfaat.
Mari kita ambil dua kasing: mengurutkan berdasarkan jarak atau memilah daftar item yang memenuhi batasan jangkauan.
# 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)
Hal pertama yang perlu kita ingat adalah bahwa kita menggunakan Pythagoras untuk menghitung jarak ( dist = sqrt(x^2 + y^2 + z^2)
) jadi kita membuat banyak sqrt
panggilan. Matematika 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
Singkatnya: sampai kita benar-benar membutuhkan jarak dalam satuan X daripada X ^ 2, kita dapat menghilangkan bagian tersulit dari perhitungan.
# 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)
Hebat, kedua fungsi tidak lagi melakukan akar kuadrat mahal. Itu akan jauh lebih cepat. Kami juga dapat meningkatkan in_range dengan mengonversinya menjadi generator:
def in_range(origin, range, things):
range_sq = range**2
yield from (thing for thing in things
if distance_sq(origin, thing) <= range_sq)
Ini terutama memiliki manfaat jika Anda melakukan sesuatu seperti:
if any(in_range(origin, max_dist, things)):
...
Tetapi jika hal berikutnya yang akan Anda lakukan membutuhkan jarak,
for nearby in in_range(origin, walking_distance, hotdog_stands):
print("%s %.2fm" % (nearby.name, distance(origin, nearby)))
pertimbangkan menghasilkan tupel:
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)
Ini bisa sangat berguna jika Anda dapat melakukan serangkaian cek ('temukan hal-hal yang mendekati X dan dalam Nm dari Y', karena Anda tidak perlu menghitung jarak lagi).
Tetapi bagaimana jika kita mencari daftar yang sangat besar things
dan kita mengantisipasi banyak dari mereka tidak layak dipertimbangkan?
Sebenarnya ada optimasi yang sangat sederhana:
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
Apakah ini berguna akan tergantung pada ukuran 'barang'.
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 ...
Dan lagi, pertimbangkan untuk menghasilkan dist_sq. Contoh hotdog kami kemudian menjadi:
# 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))