รับรายการจำนวนเต็มฉันต้องการค้นหาว่าตัวเลขใดที่ใกล้เคียงกับตัวเลขที่ฉันป้อนมากที่สุด:
>>> myList = [4, 1, 88, 44, 3]
>>> myNumber = 5
>>> takeClosest(myList, myNumber)
...
4
มีวิธีที่รวดเร็วในการทำเช่นนี้?
รับรายการจำนวนเต็มฉันต้องการค้นหาว่าตัวเลขใดที่ใกล้เคียงกับตัวเลขที่ฉันป้อนมากที่สุด:
>>> myList = [4, 1, 88, 44, 3]
>>> myNumber = 5
>>> takeClosest(myList, myNumber)
...
4
มีวิธีที่รวดเร็วในการทำเช่นนี้?
คำตอบ:
หากเราไม่แน่ใจว่ามีการเรียงลำดับรายการเราสามารถใช้ฟังก์ชันในตัวmin()
เพื่อค้นหาองค์ประกอบที่มีระยะทางขั้นต่ำจากจำนวนที่ระบุ
>>> min(myList, key=lambda x:abs(x-myNumber))
4
หมายเหตุว่ามันยังทำงานร่วมกับ dicts ด้วยปุ่ม int {1: "a", 2: "b"}
เช่น วิธีนี้ใช้เวลา O (n)
หากรายการถูกเรียงลำดับแล้วหรือคุณสามารถชำระราคาของการเรียงลำดับอาร์เรย์ได้เพียงครั้งเดียวให้ใช้วิธีการแบ่งครึ่งที่แสดงในคำตอบของ @ Lauritzซึ่งใช้เวลา O (log n) เท่านั้น (หมายเหตุอย่างไรก็ตามการตรวจสอบว่ารายการเรียงลำดับแล้วหรือไม่ (n) และการเรียงลำดับคือ O (n log n))
O(n)
ที่การแฮ็กเพียงเล็กน้อยbisect
จะช่วยให้คุณได้รับการปรับปรุงอย่างมากO(log n)
(หากมีการจัดเรียงอาร์เรย์อินพุตของคุณ)
min
เรียกใช้ผ่านพจนานุกรม ( items()
) แทนที่จะเป็นรายการและส่งคืนคีย์แทนค่าในที่สุด
ฉันจะเปลี่ยนชื่อฟังก์ชั่นtake_closest
ให้เป็นไปตามอนุสัญญาการตั้งชื่อ PEP8
หากคุณหมายถึงการใช้งานแบบด่วนซึ่งตรงกันข้ามกับการเขียนอย่างรวดเร็วไม่min
ควรเป็นอาวุธที่คุณเลือกยกเว้นในกรณีการใช้งานที่แคบมาก การแก้ปัญหาจำเป็นต้องตรวจสอบทุกหมายเลขในรายการและทำการคำนวณสำหรับแต่ละหมายเลข การใช้งานแทนจะเร็วขึ้นเกือบตลอดเวลาmin
bisect.bisect_left
"เกือบ" มาจากความจริงที่ว่าbisect_left
ต้องมีการเรียงลำดับรายการเพื่อทำงาน หวังว่ากรณีการใช้งานของคุณจะช่วยให้คุณสามารถเรียงลำดับรายการได้เพียงครั้งเดียวแล้วทิ้งไว้คนเดียว แม้ว่าจะไม่ได้ตราบใดที่คุณไม่จำเป็นต้องเรียงลำดับก่อนครั้งที่คุณโทรทุกtake_closest
ที่bisect
โมดูลจะมีโอกาสออกมาด้านบน หากคุณมีข้อสงสัยลองทั้งคู่และดูความแตกต่างในโลกแห่งความเป็นจริง
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 ทำงานโดยแบ่งครึ่งรายการและค้นหาครึ่งที่myNumber
จะต้องโดยดูที่ค่ากลาง วิธีการนี้จะมีเวลาทำงานของO (log n)เมื่อเทียบกับO (n)เวลาการทำงานของคำตอบที่ได้รับการโหวตสูงสุด หากเราเปรียบเทียบทั้งสองวิธีและจัดหาทั้งคู่ด้วยการเรียงลำดับผลลัพธ์myList
เหล่านี้คือ:
$ python -m timeit -s " จากการนำเข้าที่ใกล้เคียงที่สุด take_closest จากการนำเข้าสุ่ม randint a = range (-1000, 1000, 10) "" take_closest (a, randint (-1100, 1100)) " 100,000 ลูป, ดีที่สุดคือ 3: 2.22 usec ต่อลูป $ python -m timeit -s " จากการนำเข้าที่ใกล้เคียงที่สุด with_min จากการนำเข้าสุ่ม randint a = range (-1000, 1000, 10) "" with_min (a, randint (-1100, 1100)) " 10000 ลูปดีที่สุด 3: 43.9 usec ต่อลูป
ดังนั้นในการทดสอบbisect
นี้เร็วกว่าเกือบ 20 เท่า สำหรับรายการที่ยาวกว่าความแตกต่างจะมากขึ้น
เกิดอะไรขึ้นถ้าเราปรับระดับการเล่นโดยการลบเงื่อนไขที่myList
ต้องเรียงลำดับ? สมมติว่าเราจัดเรียงสำเนาของรายการทุกครั้งที่ take_closest
ถูกเรียกใช้ในขณะที่ปล่อยให้min
โซลูชันไม่เปลี่ยนแปลง เมื่อใช้รายการ 200 ข้อในการทดสอบด้านบนbisect
โซลูชันจะยังคงเร็วที่สุด แต่ประมาณ 30% เท่านั้น
นี่เป็นผลลัพธ์ที่แปลกเมื่อพิจารณาว่าขั้นตอนการเรียงลำดับคือO (n log (n)) ! เหตุผลเดียวที่min
ยังคงสูญเสียคือการเรียงลำดับทำในรหัส c ที่ปรับให้เหมาะสมสูงสุดในขณะที่min
ต้องทำการเรียกฟังก์ชั่นแลมบ์ดาสำหรับทุกรายการ เมื่อmyList
ขนาดเพิ่มขึ้นmin
โซลูชันจะเร็วขึ้นในที่สุด โปรดทราบว่าเราต้องซ้อนทุกอย่างเพื่อให้ได้min
ทางออก
a=range(-1000,1000,2);random.shuffle(a)
คุณจะพบว่าtakeClosest(sorted(a), b)
จะช้าลง
getClosest
อาจมีการเรียกมากกว่าหนึ่งครั้งสำหรับการเรียงลำดับทุกครั้งสิ่งนี้จะเร็วขึ้นและสำหรับกรณีการเรียงลำดับครั้งเดียวมันไม่ใช่เรื่องง่าย
myList
มีอยู่แล้วการnp.array
ใช้np.searchsorted
แทนที่bisect
เร็วกว่า
>>> takeClosest = lambda num,collection:min(collection,key=lambda x:abs(x-num))
>>> takeClosest(5,[4,1,88,44,3])
4
แลมบ์ดาเป็นลักษณะพิเศษของการเขียน "ไม่ระบุชื่อ" ฟังก์ชั่น (ฟังก์ชั่นที่ไม่ได้มีชื่อ) คุณสามารถกำหนดชื่อใด ๆ ที่คุณต้องการเพราะแลมบ์ดาเป็นนิพจน์
วิธีการ "ยาว" ในการเขียนข้างต้นจะเป็น:
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))
รหัสนี้จะให้ดัชนีของจำนวนที่ใกล้เคียงที่สุดในรายการ
วิธีแก้ปัญหาที่ KennyTM ให้ไว้นั้นดีที่สุดโดยรวม แต่ในกรณีที่คุณไม่สามารถใช้งานได้ (เช่น brython) ฟังก์ชั่นนี้จะใช้งานได้
ทำซ้ำรายการและเปรียบเทียบจำนวนที่ใกล้เคียงที่สุดในปัจจุบันด้วย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];
ควรจะเป็น เก็บค่านั้นไว้ล่วงหน้าดีกว่า
เป็นสิ่งสำคัญที่จะต้องทราบว่าแนวคิดข้อเสนอแนะของ Lauritz ในการใช้งาน bisect นั้นไม่พบค่าที่ใกล้เคียงที่สุดใน MyList to MyNumber แต่แบ่งครึ่งพบความคุ้มค่าต่อไปในการสั่งซื้อหลังจาก myNumber ใน MyList ดังนั้นในกรณีของ OP คุณจะได้รับตำแหน่ง 44 คืนมาแทนตำแหน่งที่ 4
>>> myList = [1, 3, 4, 44, 88]
>>> myNumber = 5
>>> pos = (bisect_left(myList, myNumber))
>>> myList[pos]
...
44
ในการรับค่าที่ใกล้เคียงกับ 5 คุณสามารถลองแปลงรายการเป็นอาร์เรย์และใช้ argmin จาก numpy อย่างนั้น
>>> 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
ฉันไม่รู้ว่ามันจะเร็วแค่ไหนฉันเดาว่า "ไม่มาก"
np.searchsorted
bisect_left
และ @Kanat เป็นสิทธิ - วิธีการแก้ปัญหาของ Lauritz ไม่รวมรหัสที่หยิบที่ของทั้งสองผู้สมัครเป็นผู้ใกล้ชิด
ขยายความตามคำตอบของ Gustavo Lima สิ่งเดียวกันสามารถทำได้โดยไม่ต้องสร้างรายการใหม่ทั้งหมด ค่าในรายการสามารถถูกแทนที่ด้วยส่วนต่างตามFOR
ลูปที่ดำเนินไป
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.
ถ้าฉันอาจเพิ่มคำตอบของ @ Lauritz
เพื่อไม่ให้เกิดข้อผิดพลาดในการทำงานอย่าลืมเพิ่มเงื่อนไขก่อนbisect_left
บรรทัด:
if (myNumber > myList[-1] or myNumber < myList[0]):
return False
ดังนั้นรหัสเต็มจะมีลักษณะ:
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