Đưa ra một danh sách các số nguyên, tôi muốn tìm số nào gần nhất với số tôi đưa vào đầu vào:
>>> myList = [4, 1, 88, 44, 3]
>>> myNumber = 5
>>> takeClosest(myList, myNumber)
...
4
Có cách nào nhanh chóng để làm điều này?
Đưa ra một danh sách các số nguyên, tôi muốn tìm số nào gần nhất với số tôi đưa vào đầu vào:
>>> myList = [4, 1, 88, 44, 3]
>>> myNumber = 5
>>> takeClosest(myList, myNumber)
...
4
Có cách nào nhanh chóng để làm điều này?
Câu trả lời:
Nếu chúng ta không chắc chắn rằng danh sách được sắp xếp, chúng ta có thể sử dụng được xây dựng trong min()
chức năng , để tìm các yếu tố trong đó có khoảng cách tối thiểu từ số lượng quy định.
>>> min(myList, key=lambda x:abs(x-myNumber))
4
Lưu ý rằng nó cũng hoạt động với dicts với phím int, như {1: "a", 2: "b"}
. Phương pháp này mất thời gian O (n).
Nếu danh sách đã được sắp xếp hoặc bạn chỉ có thể trả giá cho việc sắp xếp mảng một lần, hãy sử dụng phương pháp chia đôi được minh họa trong câu trả lời của @ Lauritz , chỉ mất thời gian O (log n) (lưu ý kiểm tra xem danh sách đã được sắp xếp chưa (n) và sắp xếp là O (n log n).)
O(n)
nơi mà một chút hack bisect
sẽ mang đến cho bạn một sự cải tiến lớn O(log n)
(nếu mảng đầu vào của bạn được sắp xếp).
min
, chạy nó qua từ điển ( items()
) thay vì danh sách và trả lại khóa thay vì giá trị cuối cùng.
Tôi sẽ đổi tên hàm take_closest
để phù hợp với quy ước đặt tên PEP8.
Nếu bạn có nghĩa là thực hiện nhanh trái ngược với viết nhanh, min
thì đó không phải là vũ khí bạn chọn, ngoại trừ trong một trường hợp sử dụng rất hẹp. Các min
giải pháp cần phải kiểm tra tất cả các số trong danh sách và làm một phép tính cho mỗi số. Sử dụng bisect.bisect_left
thay thế hầu như luôn luôn nhanh hơn.
"Gần như" xuất phát từ thực tế bisect_left
đòi hỏi danh sách phải được sắp xếp để hoạt động. Hy vọng, trường hợp sử dụng của bạn là như vậy mà bạn có thể sắp xếp danh sách một lần và sau đó để nó một mình. Ngay cả khi không, miễn là bạn không cần sắp xếp trước mỗi lần bạn gọi take_closest
, bisect
mô-đun có thể sẽ xuất hiện trên đầu. Nếu bạn nghi ngờ, hãy thử cả hai và nhìn vào sự khác biệt trong thế giới thực.
from bisect import bisect_left
def take_closest(myList, myNumber):
"""
Assumes myList is sorted. Returns closest value to myNumber.
If two numbers are equally close, return the smallest number.
"""
pos = bisect_left(myList, myNumber)
if pos == 0:
return myList[0]
if pos == len(myList):
return myList[-1]
before = myList[pos - 1]
after = myList[pos]
if after - myNumber < myNumber - before:
return after
else:
return before
Bisect hoạt động bằng cách liên tục giảm một nửa danh sách và tìm ra một nửa myNumber
phải có bằng cách nhìn vào giá trị trung bình. Điều này có nghĩa là nó có thời gian chạy O (log n) trái ngược với thời gian chạy O (n) của câu trả lời được bình chọn cao nhất . Nếu chúng ta so sánh hai phương thức và cung cấp cả hai với một sắp xếp myList
, đây là kết quả:
$ python -m timeit -s " từ lần nhập gần nhất Take_closest từ randint nhập khẩu ngẫu nhiên a = phạm vi (-1000, 1000, 10) "" Take_closest (a, randint (-1100, 1100)) " 100000 vòng, tốt nhất là 3: 2,22 usec mỗi vòng $ python -m timeit -s " từ lần nhập gần nhất với_min từ randint nhập khẩu ngẫu nhiên a = phạm vi (-1000, 1000, 10) "" with_min (a, randint (-1100, 1100)) " 10000 vòng, tốt nhất là 3: 43,9 usec mỗi vòng
Vì vậy, trong thử nghiệm đặc biệt này, bisect
nhanh hơn gần 20 lần. Đối với danh sách dài hơn, sự khác biệt sẽ lớn hơn.
Điều gì xảy ra nếu chúng ta san bằng sân chơi bằng cách loại bỏ điều kiện tiên quyết myList
phải được sắp xếp? Giả sử chúng ta sắp xếp một bản sao của danh sách mỗi lần take_closest
được gọi, trong khi để min
giải pháp không bị thay đổi. Sử dụng danh sách 200 mục trong thử nghiệm trên, bisect
giải pháp vẫn là nhanh nhất, mặc dù chỉ khoảng 30%.
Đây là một kết quả kỳ lạ, xem xét rằng bước sắp xếp là O (n log (n)) ! Lý do duy nhất min
vẫn còn thua là việc sắp xếp được thực hiện theo mã c được tối ưu hóa cao, trong khi min
phải thực hiện theo cách gọi hàm lambda cho mọi mục. Khi myList
tăng kích thước, min
giải pháp cuối cùng sẽ nhanh hơn. Lưu ý rằng chúng tôi phải xếp mọi thứ có lợi cho min
giải pháp để giành chiến thắng.
a=range(-1000,1000,2);random.shuffle(a)
bạn sẽ thấy điều đó takeClosest(sorted(a), b)
sẽ trở nên chậm hơn.
getClosest
có thể được gọi nhiều hơn một lần cho mỗi loại, điều này sẽ nhanh hơn và đối với trường hợp sử dụng một lần, đó là điều không có trí tuệ.
myList
đã là một np.array
thì sử dụng np.searchsorted
thay thế bisect
nhanh hơn.
>>> takeClosest = lambda num,collection:min(collection,key=lambda x:abs(x-num))
>>> takeClosest(5,[4,1,88,44,3])
4
Một lambda là một cách đặc biệt viết một chức năng "ẩn danh" (một chức năng mà không có một tên). Bạn có thể gán cho nó bất kỳ tên nào bạn muốn vì lambda là một biểu thức.
Cách viết "dài" ở trên sẽ là:
def takeClosest(num,collection):
return min(collection,key=lambda x:abs(x-num))
def closest(list, Number):
aux = []
for valor in list:
aux.append(abs(Number-valor))
return aux.index(min(aux))
Mã này sẽ cung cấp cho bạn chỉ mục về số Số gần nhất trong danh sách.
Giải pháp được đưa ra bởi KennyTM là tổng thể tốt nhất, nhưng trong trường hợp bạn không thể sử dụng nó (như brython), chức năng này sẽ thực hiện công việc
Lặp lại danh sách và so sánh số gần nhất hiện tại với abs(currentNumber - myNumber)
:
def takeClosest(myList, myNumber):
closest = myList[0]
for i in range(1, len(myList)):
if abs(i - myNumber) < closest:
closest = i
return closest
if abs(myList[i] - myNumber) < abs(closest - myNumber): closest = myList[i];
. Tốt hơn lưu trữ giá trị đó trước mặc dù.
Điều quan trọng cần lưu ý là ý tưởng đề xuất sử dụng bisect của Lauritz không thực sự tìm thấy giá trị gần nhất trong MyList cho MyNumber. Thay vào đó, bisect tìm giá trị tiếp theo theo thứ tự sau MyNumber trong MyList. Vì vậy, trong trường hợp của OP, bạn thực sự có được vị trí 44 được trả về thay vì vị trí 4.
>>> myList = [1, 3, 4, 44, 88]
>>> myNumber = 5
>>> pos = (bisect_left(myList, myNumber))
>>> myList[pos]
...
44
Để có được giá trị gần nhất với 5, bạn có thể thử chuyển đổi danh sách thành một mảng và sử dụng argmin từ numpy như vậy.
>>> import numpy as np
>>> myNumber = 5
>>> myList = [1, 3, 4, 44, 88]
>>> myArray = np.array(myList)
>>> pos = (np.abs(myArray-myNumber)).argmin()
>>> myArray[pos]
...
4
Tôi không biết điều này sẽ nhanh đến mức nào, tôi đoán là "không" lắm.
np.searchsorted
thay vì bisect_left
. Và @Kanat là đúng - giải pháp Lauritz của không bao gồm mã đó chọn một trong hai ứng cử viên là gần gũi hơn.
Mở rộng dựa trên câu trả lời của Gustavo Lima. Điều tương tự có thể được thực hiện mà không cần tạo một danh sách hoàn toàn mới. Các giá trị trong danh sách có thể được thay thế bằng các vi sai khi FOR
vòng lặp tiến triển.
def f_ClosestVal(v_List, v_Number):
"""Takes an unsorted LIST of INTs and RETURNS INDEX of value closest to an INT"""
for _index, i in enumerate(v_List):
v_List[_index] = abs(v_Number - i)
return v_List.index(min(v_List))
myList = [1, 88, 44, 4, 4, -2, 3]
v_Num = 5
print(f_ClosestVal(myList, v_Num)) ## Gives "3," the index of the first "4" in the list.
Nếu tôi có thể thêm vào câu trả lời của @ Lauritz
Để không xảy ra lỗi chạy, đừng quên thêm một điều kiện trước bisect_left
dòng:
if (myNumber > myList[-1] or myNumber < myList[0]):
return False
vì vậy mã đầy đủ sẽ trông như sau:
from bisect import bisect_left
def takeClosest(myList, myNumber):
"""
Assumes myList is sorted. Returns closest value to myNumber.
If two numbers are equally close, return the smallest number.
If number is outside of min or max return False
"""
if (myNumber > myList[-1] or myNumber < myList[0]):
return False
pos = bisect_left(myList, myNumber)
if pos == 0:
return myList[0]
if pos == len(myList):
return myList[-1]
before = myList[pos - 1]
after = myList[pos]
if after - myNumber < myNumber - before:
return after
else:
return before