Come bloccare un intero su un certo intervallo?


92

Ho il codice seguente:

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]

Fondamentalmente, calcolo un nuovo indice e lo uso per trovare qualche elemento da un elenco. Per assicurarmi che l'indice fosse all'interno dei limiti dell'elenco, avevo bisogno di scrivere quelle 2 ifistruzioni distribuite su 4 righe. È piuttosto prolisso, un po 'brutto ... Oserei dire, è abbastanza non pitonico .

C'è qualche altra soluzione più semplice e compatta? (e più pitonico )

Sì, lo so che posso usare if elsein una riga, ma non è leggibile:

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

So anche che posso incatenare max()e min()insieme. È più compatto, ma sento che è un po 'oscuro, più difficile da trovare bug se lo digito male. In altre parole, non lo trovo molto semplice.

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

2
Se sembra "un po 'oscuro", farne una funzione?
Babbo Natale

1
Sì, posso scrivere una funzione, ma non è questo il punto. La domanda è come implementarlo (inline o in una funzione).
Denilson Sá Maia,

clamp = lambda value, minv, maxv: max(min(value, maxv), minv)Utilizzando l'API da arma.sourceforge.net/docs.html#clamp
Dima Tisnek

Risposte:


119

Questo è abbastanza chiaro, in realtà. Molte persone lo imparano velocemente. Puoi usare un commento per aiutarli.

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

12
Anche se sento che non è così pitonico come dovrebbe essere, sento anche che questa è la migliore soluzione che abbiamo ora.
Denilson Sá Maia

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

3
@csl Folks fornisce sempre queste piccole funzioni di supporto, ma non so mai dove metterle. helperFunctions.py? Un modulo separato? E se questo fosse disseminato di varie "funzioni di aiuto" per cose completamente diverse?
Mateen Ulhaq

1
Non lo so, ma se ne raccogli molti e li classifichi in moduli ragionevoli, perché non mettere su GitHub e creare un pacchetto PyPi da esso? Probabilmente sarebbe diventato popolare.
csl

@MateenUlhaqutils.py
Wouterr

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

per esempio:

>>> 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

10
+1 per l'utilizzo creativo di sorted()built-in. Molto compatto, ma è solo un po 'oscuro. Comunque è sempre bello vedere altre soluzioni creative!
Denilson Sá Maia

10
Molto creativo e in realtà veloce quanto la min(max())costruzione. Molto leggermente più veloce nel caso in cui il numero sia compreso nell'intervallo e non siano necessari swap.
kindall

40

molte risposte interessanti qui, tutte quasi uguali, tranne ... quale è più veloce?

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 ce l' ha !, usa il semplice pitone. La versione numpy è, forse non sorprendentemente, la più lenta di tutte. Probabilmente perché sta cercando array, dove le altre versioni ordinano semplicemente i loro argomenti.


7
@LenarHoyt non è così sorprendente, considerando che le prestazioni di Numpy sono progettate attorno a grandi array, non singoli numeri. Inoltre, deve prima convertire l'intero in un tipo di dati interno e poiché accetta diversi tipi di input, probabilmente ci vuole molto tempo per capire di che tipo è l'input e in cosa convertirlo. Vedrai prestazioni di Numpy molto migliori se fornisci un array (preferibilmente non un elenco o una tupla, che deve prima convertire) di diverse migliaia di valori.
blubberdiblub

Python è tre ordini di grandezza più lento. 783 ns = 783.000 µs. Ho fatto lo stesso errore in passato. La notazione è sottile.
Dustin Andrews

5
@DustinAndrews hai capito al contrario. 1 µs è 10 ^ -6 secondi, 1 ns è 10 ^ -9 secondi. l'esempio di python completa 1 ciclo in 0,784 µs. O almeno, lo ha fatto sulla macchina su cui l'ho provato. Questo microbenchmark è utile quanto qualsiasi altro microbenchmark; può indirizzarti lontano da idee davvero pessime, ma probabilmente non ti aiuterà molto a trovare il modo effettivamente più veloce per scrivere codice utile .
SingleNegationElimination

C'è un leggero sovraccarico sulla chiamata delle funzioni. Non ho fatto i benchmark, ma è del tutto possibile mm_clipe py_clipsarà altrettanto veloce se usi il compilatore JIT, come PyPy. Tranne che il primo è più leggibile e la leggibilità è più importante nella filosofia di Python di un leggero guadagno di prestazioni per la maggior parte del tempo.
Highstaker

@DustinAndrews ti consiglio di cancellare il tuo commento effettivamente errato perché l'hai ricevuto al contrario.
Acumenus

38

Vedi numpy.clip :

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

I documenti dicono che il primo parametro di clipè a, un "array contenente elementi da ritagliare". Quindi dovresti scrivere numpy.clip([index], …, no numpy.clip(index, ….
Rory O'Kane

13
@ RoryO'Kane: l'hai provato?
Neil G

1
Pandas lo consente anche su serie, dataframe e pannelli.
Nour Wolf

17

Incatenare max()e min()insieme è l'idioma normale che ho visto. Se trovi difficile leggere, scrivi una funzione di supporto per incapsulare l'operazione:

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

14

Che fine ha fatto il mio amato linguaggio leggibile Python? :-)

Seriamente, rendilo una funzione:

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

quindi chiamalo con qualcosa come:

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

O una soluzione più semplice e flessibile in cui esegui il calcolo da solo:

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

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

Se lo desideri, potresti persino creare un elenco di min / max in modo che appaia più "matematicamente puro":

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

6
Metterlo in una funzione va bene (e consigliato, se lo fai spesso ), ma penso mine maxsono molto più chiari di un mucchio di condizionali. (Non so a cosa addserva - dillo solo clamp(val + 7, 0, 42).)
Glenn Maynard

1
@GlennMaynard. Non sono sicuro di poter essere d'accordo sul fatto che min e max siano più puliti. Il punto centrale del loro utilizzo è essere in grado di riempire di più su una riga, rendendo effettivamente il codice meno leggibile.
Fisico pazzo

10

Questo mi sembra più pitonico:

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

Alcuni test:

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

8

Se il tuo codice sembra troppo ingombrante, una funzione potrebbe aiutare:

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

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

2

Evita di scrivere funzioni per attività così piccole, a meno che non le applichi spesso, poiché ingombrerà il tuo codice.

per valori individuali:

min(clamp_max, max(clamp_min, value))

per elenchi di valori:

map(lambda x: min(clamp_max, max(clamp_min, x)), values)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.