จะหนีบจำนวนเต็มกับบางช่วงได้อย่างไร?


94

ฉันมีรหัสต่อไปนี้:

new_index = index + offset
if new_index < 0:
    new_index = 0
if new_index >= len(mylist):
    new_index = len(mylist) - 1
return mylist[new_index]

โดยทั่วไปฉันคำนวณดัชนีใหม่และใช้สิ่งนั้นเพื่อค้นหาองค์ประกอบบางอย่างจากรายการ เพื่อให้แน่ใจว่าดัชนีอยู่ในขอบเขตของรายการฉันต้องเขียนifคำสั่ง2 คำนี้โดยกระจายเป็น 4 บรรทัด มันค่อนข้างฟุ่มเฟื่อยน่าเกลียดไปหน่อย ... ฉันบอกเลยว่ามันค่อนข้างไม่พีค

มีโซลูชันอื่นที่ง่ายและกะทัดรัดกว่านี้หรือไม่? (และpythonicอื่น ๆ)

ใช่ฉันรู้ว่าสามารถใช้ได้if elseในบรรทัดเดียว แต่อ่านไม่ออก:

new_index = 0 if new_index < 0 else len(mylist) - 1 if new_index >= len(mylist) else new_index

ฉันยังรู้ว่าฉันล่ามโซ่max()และmin()อยู่ด้วยกันได้ มันกะทัดรัดกว่า แต่ฉันรู้สึกว่ามันค่อนข้างคลุมเครือหาจุดบกพร่องได้ยากกว่าถ้าฉันพิมพ์ผิด กล่าวอีกนัยหนึ่งฉันไม่พบว่ามันตรงไปตรงมามากนัก

new_index = max(0, min(new_index, len(mylist)-1))

2
ถ้ารู้สึกว่า "ค่อนข้างคลุมเครือ" ให้สร้างฟังก์ชันออกมาไหม
ซานตา

1
ใช่ฉันเขียนฟังก์ชันได้ แต่นั่นไม่ใช่ประเด็น คำถามคือวิธีการนำไปใช้ (ทั้งแบบอินไลน์หรือในฟังก์ชัน)
Denilson Sá Maia

clamp = lambda value, minv, maxv: max(min(value, maxv), minv)ใช้ API จากarma.sourceforge.net/docs.html#clamp
Dima Tisnek

คำตอบ:


121

สวยใสจริง หลายคนเรียนรู้ได้อย่างรวดเร็ว คุณสามารถใช้ความคิดเห็นเพื่อช่วยพวกเขาได้

new_index = max(0, min(new_index, len(mylist)-1))

12
แม้ว่าฉันรู้สึกว่ามันไม่ได้เป็น pythonic เท่าที่ควร แต่ฉันก็รู้สึกว่านี่เป็นทางออกที่ดีที่สุดที่เรามีในตอนนี้
Denilson Sá Maia

50
def clamp(n, smallest, largest): return max(smallest, min(n, largest))
csl

3
@csl Folks มีฟังก์ชั่นตัวช่วยเล็ก ๆ เหล่านี้อยู่เสมอ แต่ฉันไม่รู้ว่าจะใส่ไว้ที่ไหน helperFunctions.pyเหรอ? โมดูลแยกต่างหาก? จะเกิดอะไรขึ้นถ้าสิ่งนี้เกลื่อนไปด้วย "ฟังก์ชันตัวช่วย" ที่แตกต่างกันโดยสิ้นเชิง
Mateen Ulhaq

2
ฉันไม่รู้ แต่ถ้าคุณรวบรวมสิ่งเหล่านี้จำนวนมากและจัดหมวดหมู่เป็นโมดูลที่เหมาะสมทำไมไม่ใส่ GitHub และสร้างแพ็คเกจ PyPi จากมัน? ก็คงจะกลายเป็นที่นิยม
csl

@MateenUlhaqutils.py
Wouterr

90
sorted((minval, value, maxval))[1]

ตัวอย่างเช่น:

>>> minval=3
>>> maxval=7
>>> for value in range(10):
...   print sorted((minval, value, maxval))[1]
... 
3
3
3
3
4
5
6
7
7
7

12
+1 สำหรับการใช้งานที่สร้างสรรค์sorted()ในตัว กะทัดรัดมาก แต่ก็ดูคลุมเคลือไปหน่อย อย่างไรก็ตามเป็นเรื่องดีที่ได้เห็นโซลูชันที่สร้างสรรค์อื่น ๆ !
Denilson Sá Maia

12
มีความคิดสร้างสรรค์มากและเร็วพอ ๆ กับการmin(max())ก่อสร้าง เร็วขึ้นเล็กน้อยในกรณีที่หมายเลขอยู่ในช่วงและไม่จำเป็นต้องมีการแลกเปลี่ยน
kindall

41

คำตอบที่น่าสนใจมากมายที่นี่เหมือนกันหมดยกเว้น ... อันไหนเร็วกว่ากัน?

import numpy
np_clip = numpy.clip
mm_clip = lambda x, l, u: max(l, min(u, x))
s_clip = lambda x, l, u: sorted((x, l, u))[1]
py_clip = lambda x, l, u: l if x < l else u if x > u else x
>>> import random
>>> rrange = random.randrange
>>> %timeit mm_clip(rrange(100), 10, 90)
1000000 loops, best of 3: 1.02 µs per loop

>>> %timeit s_clip(rrange(100), 10, 90)
1000000 loops, best of 3: 1.21 µs per loop

>>> %timeit np_clip(rrange(100), 10, 90)
100000 loops, best of 3: 6.12 µs per loop

>>> %timeit py_clip(rrange(100), 10, 90)
1000000 loops, best of 3: 783 ns per loop

paxdiabloมีแล้ว! ใช้ python ol 'ธรรมดา รุ่นที่น่าเบื่ออาจไม่น่าแปลกใจที่ช้าที่สุดในจำนวนมาก อาจเป็นเพราะกำลังมองหาอาร์เรย์โดยที่เวอร์ชันอื่น ๆ จะสั่งอาร์กิวเมนต์


7
@ LenarHoyt ไม่น่าแปลกใจเมื่อพิจารณาว่าประสิทธิภาพของ Numpy ได้รับการออกแบบโดยใช้อาร์เรย์ขนาดใหญ่ไม่ใช่ตัวเลขเดียว นอกจากนี้ยังต้องแปลงจำนวนเต็มเป็นประเภทข้อมูลภายในก่อนและเนื่องจากยอมรับอินพุตหลายประเภทจึงอาจต้องใช้เวลาพอสมควรในการพิจารณาว่าอินพุตเป็นประเภทใดและจะแปลงเป็นอะไร คุณจะเห็นประสิทธิภาพของ Numpy ที่ดีขึ้นมากหากคุณป้อนอาร์เรย์ (ไม่ควรเป็นรายการหรือทูเพิลซึ่งต้องแปลงค่าก่อน) จากค่าต่างๆหลายพันค่า
blubberdiblub

7
@DustinAndrews คุณได้ย้อนกลับไปแล้ว 1 µs คือ 10 ^ -6 วินาที 1 ns คือ 10 ^ -9 วินาที ตัวอย่าง python เสร็จสิ้น 1 ลูปใน 0.784 µs หรืออย่างน้อยก็ทำบนเครื่องที่ฉันทดสอบ ไมโครเบนช์มาร์กนี้มีประโยชน์พอ ๆ กับไมโครเบนช์มาร์กอื่น ๆ มันสามารถชี้ให้คุณห่างไกลจากแนวคิดที่ไม่ดี แต่อาจไม่ช่วยให้คุณพบวิธีที่เร็วที่สุดในการเขียนโค้ดที่เป็นประโยชน์
SingleNegationElimination

มีค่าใช้จ่ายเล็กน้อยในการเรียกใช้ฟังก์ชัน ฉันยังไม่ได้ทำเกณฑ์มาตรฐาน แต่ค่อนข้างเป็นไปได้ที่mm_clipและpy_clipจะเร็วพอ ๆ กันถ้าคุณใช้คอมไพเลอร์ JIT เช่น PyPy ยกเว้นในอดีตนั้นสามารถอ่านได้มากกว่าและความสามารถในการอ่านมีความสำคัญในปรัชญาของ Python มากกว่าประสิทธิภาพที่เพิ่มขึ้นเล็กน้อยเกือบตลอดเวลา
Highstaker

@DustinAndrews ฉันขอแนะนำให้ลบความคิดเห็นที่ไม่ถูกต้องตามข้อเท็จจริงของคุณเพราะคุณได้รับมันย้อนกลับ
Acumenus

38

ดูnumpy.clip :

index = numpy.clip(index, 0, len(my_list) - 1)

เอกสารกล่าวว่าพารามิเตอร์แรกของclipคือa"อาร์เรย์ที่มีองค์ประกอบที่จะคลิป" ดังนั้นคุณจะต้องมีการเขียนไม่ได้numpy.clip([index], … numpy.clip(index, …
Rory O'Kane

13
@ RoryO'Kane: คุณลองหรือยัง?
Neil G

1
Pandas ยังอนุญาตให้ทำสิ่งนี้บน Series และ DataFrames และ Panels
Nour Wolf

20

การล่ามโซ่max()และการmin()อยู่ด้วยกันเป็นสำนวนปกติที่ฉันเคยเห็น หากคุณพบว่ายากที่จะอ่านให้เขียนฟังก์ชันตัวช่วยเพื่อห่อหุ้มการทำงาน:

def clamp(minimum, x, maximum):
    return max(minimum, min(x, maximum))

14

เกิดอะไรขึ้นกับภาษา Python ที่ฉันรัก :-)

อย่างจริงจังเพียงแค่ทำให้เป็นฟังก์ชัน:

def addInRange(val, add, minval, maxval):
    newval = val + add
    if newval < minval: return minval
    if newval > maxval: return maxval
    return newval

จากนั้นเรียกมันด้วยสิ่งที่ชอบ:

val = addInRange(val, 7, 0, 42)

หรือวิธีแก้ปัญหาที่ง่ายกว่ายืดหยุ่นกว่าโดยที่คุณคำนวณด้วยตัวเอง:

def restrict(val, minval, maxval):
    if val < minval: return minval
    if val > maxval: return maxval
    return val

x = restrict(x+10, 0, 42)

หากคุณต้องการคุณสามารถสร้างรายการต่ำสุด / สูงสุดเพื่อให้ดู "บริสุทธิ์ทางคณิตศาสตร์" มากขึ้น:

x = restrict(val+7, [0, 42])

6
การวางไว้ในฟังก์ชั่นนั้นใช้ได้ดี (และขอแนะนำถ้าคุณทำมันมาก) แต่ฉันคิดว่าminและmaxชัดเจนกว่าเงื่อนไขมากมาย (ฉันไม่รู้ว่าaddมีไว้เพื่ออะไร- แค่พูดclamp(val + 7, 0, 42))
Glenn Maynard

1
@GlennMaynard. ไม่แน่ใจว่าตกลงกันได้ว่า min และ max สะอาดกว่า จุดรวมของการใช้พวกเขาคือสามารถบรรจุลงในบรรทัดเดียวได้มากขึ้นทำให้โค้ดอ่านง่ายขึ้น
Mad Physicist

11

อันนี้ดูเหมือน pythonic มากกว่าสำหรับฉัน:

>>> def clip(val, min_, max_):
...     return min_ if val < min_ else max_ if val > max_ else val

การทดสอบเล็กน้อย:

>>> clip(5, 2, 7)
5
>>> clip(1, 2, 7)
2
>>> clip(8, 2, 7)
7

8

หากโค้ดของคุณดูเทอะทะเกินไปฟังก์ชันอาจช่วยได้:

def clamp(minvalue, value, maxvalue):
    return max(minvalue, min(value, maxvalue))

new_index = clamp(0, new_index, len(mylist)-1)

2

หลีกเลี่ยงการเขียนฟังก์ชันสำหรับงานเล็ก ๆ เช่นนี้เว้นแต่คุณจะใช้บ่อยๆเพราะจะทำให้โค้ดของคุณรก

สำหรับแต่ละค่า:

min(clamp_max, max(clamp_min, value))

สำหรับรายการค่า:

map(lambda x: min(clamp_max, max(clamp_min, x)), values)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.