Jak ograniczyć liczbę całkowitą do pewnego zakresu?

94

Mam następujący kod:

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]

Zasadniczo obliczam nowy indeks i używam go, aby znaleźć jakiś element z listy. Aby upewnić się, że indeks znajduje się w granicach listy, musiałem zapisać te 2 ifinstrukcje rozłożone na 4 linie. To dość rozwlekłe, trochę brzydkie ... Ośmielę się powiedzieć, to całkiem nie-pytoniczne .

Czy jest jakieś inne prostsze i bardziej kompaktowe rozwiązanie? (i bardziej pythonowe )

Tak, wiem, że mogę użyć if elsew jednej linii, ale nie jest to czytelne:

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

Wiem też, że potrafię łączyć max()i min()razem. Jest bardziej kompaktowy, ale wydaje mi się, że jest trochę niejasny, trudniej jest znaleźć błędy, jeśli wpiszę źle. Innymi słowy, nie uważam tego za bardzo proste.

new_index = max(0, min(new_index, len(mylist)-1))
Denilson Sá Maia
źródło
2
Jeśli czujesz się „trochę niejasny”, uczyń z tego jakąś funkcję?
Święty Mikołaj
1
Tak, mogę napisać funkcję, ale nie o to chodzi. Pytanie brzmi, jak to zaimplementować (w tekście lub w funkcji).
Denilson Sá Maia
clamp = lambda value, minv, maxv: max(min(value, maxv), minv)Korzystanie z API z arma.sourceforge.net/docs.html#clamp
Dima Tisnek

Odpowiedzi:

121

Właściwie jest to całkiem jasne. Wiele osób szybko się tego uczy. Możesz użyć komentarza, aby im pomóc.

new_index = max(0, min(new_index, len(mylist)-1))
S.Lott
źródło
12
Chociaż wydaje mi się, że nie jest tak pytoniczny, jak powinien, uważam też, że jest to najlepsze rozwiązanie, jakie mamy teraz.
Denilson Sá Maia
50
def clamp(n, smallest, largest): return max(smallest, min(n, largest))
csl
3
@csl Ludzie zawsze zapewniają te małe funkcje pomocnicze, ale nigdy nie wiem, gdzie je umieścić. helperFunctions.py? Osobny moduł? A co jeśli zostanie zaśmiecony różnymi „funkcjami pomocniczymi” do zupełnie innych rzeczy?
Mateen Ulhaq
2
Nie wiem, ale jeśli zbierzesz ich dużo i podzielisz je na rozsądne moduły, dlaczego nie założyć GitHub i stworzyć z tego pakietu PyPi? Prawdopodobnie stałby się popularny.
csl
@MateenUlhaqutils.py
Wouterr
90
sorted((minval, value, maxval))[1]

na przykład:

>>> 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
źródło
12
+1 za kreatywne wykorzystanie sorted()wbudowanego. Bardzo kompaktowy, ale trochę niejasny. Zresztą zawsze miło jest zobaczyć inne kreatywne rozwiązania!
Denilson Sá Maia
12
Bardzo kreatywny, a właściwie tak szybki jak min(max())konstrukcja. Bardzo nieznacznie szybciej w przypadku, gdy liczba mieści się w zakresie i nie są potrzebne żadne zamiany.
kindall
41

wiele interesujących odpowiedzi tutaj, wszystkie mniej więcej takie same, z wyjątkiem ... która jest szybsza?

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 ma to !, użyj zwykłego starego pythona. Wersja numpy jest, być może, nie jest zaskoczeniem, najwolniejsza z wielu. Prawdopodobnie dlatego, że szuka tablic, w których inne wersje po prostu porządkują swoje argumenty.

SingleNegationElimination
źródło
7
@LenarHoyt nie jest to takie zaskakujące, biorąc pod uwagę, że wydajność Numpy jest zaprojektowana wokół dużych tablic, a nie pojedynczych liczb. Ponadto musi najpierw przekonwertować liczbę całkowitą na wewnętrzny typ danych, a ponieważ akceptuje kilka różnych rodzajów danych wejściowych, prawdopodobnie zajmie dużo czasu, aby dowiedzieć się, jaki typ jest wejście i na co je przekształcić. Zauważysz znacznie lepszą wydajność Numpy, jeśli podasz mu tablicę (najlepiej nie listę lub krotkę, którą musi najpierw przekonwertować) zawierającą kilka tysięcy wartości.
blubberdiblub
7
@DustinAndrews, masz to od tyłu. 1 µs to 10 ^ -6 sekund, 1 ns to 10 ^ -9 sekund. przykład w Pythonie kończy 1 pętlę w czasie 0,784 µs. A przynajmniej tak się stało na maszynie, na której to przetestowałem. Ten mikroznak jest tak samo przydatny jak każdy inny mikroznak; może odciągnąć Cię od naprawdę złych pomysłów, ale prawdopodobnie nie pomoże Ci znaleźć najszybszego sposobu na napisanie użytecznego kodu.
SingleNegationElimination
Istnieje niewielki narzut na wywołanie funkcji. Nie zrobiłem kryteriów, ale jest to całkiem możliwe, że mm_clipi py_clipbędzie równie szybki, jeśli używasz kompilatora JIT, jak pypy. Z wyjątkiem tego, że pierwszy jest bardziej czytelny, a czytelność jest ważniejsza w filozofii Pythona niż niewielki wzrost wydajności przez większość czasu.
Highstaker
@DustinAndrews Radzę usunąć Twój merytorycznie niepoprawny komentarz, ponieważ zrobiłeś to wstecz.
Acumenus
38

Zobacz numpy.clip :

index = numpy.clip(index, 0, len(my_list) - 1)
Neil G.
źródło
Dokumenty mówią, że pierwszym parametrem clipjest a„tablica zawierająca elementy do przycięcia”. Więc musiałbyś pisać numpy.clip([index], …, nie numpy.clip(index, ….
Rory O'Kane
13
@ RoryO'Kane: Próbowałeś tego?
Neil G
1
Pandy zezwalają na to również w seriach, ramkach danych i panelach.
Nour Wolf
20

Łączenie max()i min()razem to normalny idiom, jaki widziałem. Jeśli masz trudności z odczytaniem, napisz funkcję pomocniczą, aby hermetyzować operację:

def clamp(minimum, x, maximum):
    return max(minimum, min(x, maximum))
Laurence Gonsalves
źródło
14

Cokolwiek stało się z moim ukochanym, czytelnym językiem Pythona? :-)

Poważnie, po prostu zrób z tego funkcję:

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

następnie po prostu nazwij to czymś takim:

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

Lub prostsze, bardziej elastyczne rozwiązanie, w którym samodzielnie wykonujesz obliczenia:

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

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

Jeśli chcesz, możesz nawet utworzyć listę min / max, aby wyglądała bardziej „matematycznie czysto”:

x = restrict(val+7, [0, 42])
paxdiablo
źródło
6
Umieszczenie go w funkcji jest w porządku (i jest zalecane, jeśli robisz to dużo), ale myślę mini maxjest o wiele jaśniejsze niż kilka warunków. (Nie wiem, do czego addsłuży - po prostu powiedz clamp(val + 7, 0, 42).)
Glenn Maynard
1
@GlennMaynard. Nie jestem pewien, czy mogę się zgodzić, że min i max są czystsze. Cały sens ich używania polega na tym, aby móc umieścić więcej w jednym wierszu, skutecznie czyniąc kod mniej czytelnym.
Mad Physicist
11

Ten wydaje mi się bardziej pytoniczny:

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

Kilka testów:

>>> clip(5, 2, 7)
5
>>> clip(1, 2, 7)
2
>>> clip(8, 2, 7)
7
Jens
źródło
8

Jeśli twój kod wydaje się zbyt nieporęczny, funkcja może pomóc:

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

new_index = clamp(0, new_index, len(mylist)-1)
Greg Hewgill
źródło
2

Unikaj pisania funkcji dla tak małych zadań, chyba że używasz ich często, ponieważ zaśmieca to twój kod.

dla poszczególnych wartości:

min(clamp_max, max(clamp_min, value))

dla list wartości:

map(lambda x: min(clamp_max, max(clamp_min, x)), values)
Jetze Schaafsma
źródło