Bagaimana cara menjepit integer ke beberapa rentang?

94

Saya memiliki kode berikut:

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]

Pada dasarnya, saya menghitung indeks baru dan menggunakannya untuk menemukan beberapa elemen dari daftar. Untuk memastikan indeks berada di dalam batas-batas daftar, saya perlu menulis 2 ifpernyataan itu tersebar menjadi 4 baris. Itu cukup bertele-tele, agak jelek ... Berani saya katakan, ini sangat tidak pythonic .

Apakah ada solusi lain yang lebih sederhana dan lebih ringkas? (dan lebih banyak lagi pythonic )

Ya, saya tahu saya dapat menggunakan if elsedalam satu baris, tetapi tidak dapat dibaca:

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

Saya juga tahu saya bisa merangkai max()dan min()bersama. Lebih ringkas, tapi saya rasa agak kabur, lebih sulit menemukan bug jika saya salah mengetik. Dengan kata lain, saya tidak menganggapnya sangat mudah.

new_index = max(0, min(new_index, len(mylist)-1))
Denilson Sá Maia
sumber
2
Jika terasa "agak tidak jelas", buat fungsi darinya?
Santa
1
Ya, saya bisa menulis fungsi, tapi bukan itu intinya. Pertanyaannya adalah bagaimana mengimplementasikannya (baik sebaris atau dalam fungsi).
Denilson Sá Maia
clamp = lambda value, minv, maxv: max(min(value, maxv), minv)Menggunakan API dari arma.sourceforge.net/docs.html#clamp
Dima Tisnek

Jawaban:

121

Ini sebenarnya cukup jelas. Banyak orang mempelajarinya dengan cepat. Anda dapat menggunakan komentar untuk membantu mereka.

new_index = max(0, min(new_index, len(mylist)-1))
S. Lott
sumber
12
Meskipun saya merasa ini tidak sesempit yang seharusnya, saya juga merasa ini adalah solusi terbaik yang kami miliki sekarang.
Denilson Sá Maia
50
def clamp(n, smallest, largest): return max(smallest, min(n, largest))
csl
3
@csl Folks selalu menyediakan fungsi pembantu kecil ini, tetapi saya tidak pernah tahu di mana harus menaruhnya. helperFunctions.py? Modul terpisah? Bagaimana jika ini dikotori dengan berbagai "fungsi pembantu" untuk hal yang sama sekali berbeda?
Mateen Ulhaq
2
Entahlah, tetapi jika Anda mengumpulkan banyak dari mereka dan mengkategorikannya ke dalam modul yang masuk akal, mengapa tidak memakai GitHub dan membuat paket PyPi darinya? Mungkin akan menjadi populer.
csl
@Admin_lhautils.py
Wouterr
90
sorted((minval, value, maxval))[1]

sebagai contoh:

>>> 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
John La Rooy
sumber
12
+1 untuk penggunaan kreatif bawaan sorted(). Sangat kompak, tetapi hanya sedikit tidak jelas. Bagaimanapun, selalu menyenangkan melihat solusi kreatif lainnya!
Denilson Sá Maia
12
Sangat kreatif, dan sebenarnya secepat min(max())pembangunannya. Sangat sedikit lebih cepat jika angkanya berada dalam kisaran dan tidak diperlukan swap.
kindall
41

banyak jawaban menarik di sini, semuanya hampir sama, kecuali ... mana yang lebih cepat?

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 memilikinya !, gunakan python ol biasa. Versi numpy, mungkin tidak mengherankan, adalah yang paling lambat dari semuanya. Mungkin karena mencari array, di mana versi lain hanya mengurutkan argumennya.

SingleNegationElimination
sumber
7
@LenarHoyt tidak terlalu mengherankan, mengingat kinerja Numpy dirancang di sekitar array besar, bukan angka tunggal. Selain itu, ia harus mengonversi integer ke tipe data internal terlebih dahulu dan karena ia menerima beberapa jenis masukan yang berbeda, mungkin perlu waktu yang cukup lama untuk mengetahui jenis masukan dan apa yang akan diubah menjadi. Anda akan melihat kinerja Numpy yang jauh lebih baik jika Anda memberinya sebuah array (sebaiknya bukan daftar atau tuple, yang harus diubah terlebih dahulu) dari beberapa ribu nilai.
blubberdiblub
Python tiga kali lipat lebih lambat. 783 ns = 783.000 µs. Saya telah membuat kesalahan yang sama di masa lalu. Notasinya halus.
Dustin Andrews
7
@DinAndonesia Anda punya itu terbalik. 1 µs adalah 10 ^ -6 detik, 1 ns adalah 10 ^ -9 detik. contoh python menyelesaikan 1 loop dalam 0,784 µs. Atau setidaknya, itu terjadi pada mesin yang saya uji. Microbenchmark ini berguna seperti microbenchmark lainnya; ini dapat membuat Anda menjauh dari ide yang sangat buruk tetapi mungkin tidak akan banyak membantu Anda menemukan cara tercepat yang sebenarnya untuk menulis kode yang berguna .
SingleNegationElimination
Ada sedikit biaya tambahan pada pemanggilan fungsi. Saya belum melakukan benchmark, tetapi sangat mungkin mm_clipdan py_clipakan sama cepatnya jika Anda menggunakan compiler JIT, seperti PyPy. Kecuali yang pertama lebih mudah dibaca, dan keterbacaan lebih penting dalam filosofi Python daripada sedikit peningkatan kinerja pada sebagian besar waktu.
Highstaker
@DustinAndrews Saya menyarankan untuk menghapus komentar Anda yang salah faktual karena Anda mendapatkannya mundur.
Acumenus
38

Lihat numpy.clip :

index = numpy.clip(index, 0, len(my_list) - 1)
Neil G
sumber
Dokumen mengatakan parameter pertama clipadalah a, "array yang berisi elemen untuk dijepit". Jadi, Anda harus menulis numpy.clip([index], …, bukan numpy.clip(index, ….
Rory O'Kane
13
@ RoryO'Kane: Apakah Anda mencobanya?
Neil G
1
Pandas juga mengizinkan ini di Seri dan DataFrames, dan Panel.
Nour Wolf
19

Merantai max()dan min()bersama adalah ungkapan normal yang pernah saya lihat. Jika Anda kesulitan membaca, tulis fungsi pembantu untuk merangkum operasi:

def clamp(minimum, x, maximum):
    return max(minimum, min(x, maximum))
Laurence Gonsalves
sumber
14

Apa yang terjadi dengan bahasa Python saya yang mudah dibaca? :-)

Serius, jadikan itu fungsi:

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

kemudian sebut saja dengan sesuatu seperti:

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

Atau solusi yang lebih sederhana dan lebih fleksibel di mana Anda melakukan penghitungan sendiri:

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

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

Jika Anda mau, Anda bahkan dapat membuat daftar min / max sehingga terlihat lebih "murni secara matematis":

x = restrict(val+7, [0, 42])
paxdiablo
sumber
6
Menempatkannya dalam suatu fungsi baik-baik saja (dan disarankan, jika Anda sering melakukannya), tetapi saya pikir mindan maxjauh lebih jelas daripada sekumpulan persyaratan. (Saya tidak tahu untuk apa add- katakan saja clamp(val + 7, 0, 42).)
Glenn Maynard
1
@Tokopedia Tidak yakin saya setuju bahwa min dan max lebih bersih. Inti dari menggunakannya adalah untuk dapat memasukkan lebih banyak ke satu baris, secara efektif membuat kode kurang terbaca.
Mad Physicist
11

Yang ini tampaknya lebih pythonic bagi saya:

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

Beberapa tes:

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

Jika kode Anda tampak terlalu berat, suatu fungsi mungkin membantu:

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

new_index = clamp(0, new_index, len(mylist)-1)
Greg Hewgill
sumber
2

Hindari menulis fungsi untuk tugas-tugas kecil seperti itu, kecuali jika Anda sering menerapkannya, karena akan mengacaukan kode Anda.

untuk nilai-nilai individu:

min(clamp_max, max(clamp_min, value))

untuk daftar nilai:

map(lambda x: min(clamp_max, max(clamp_min, x)), values)
Jetze Schaafsma
sumber