¿Cómo sujetar un número entero a algún rango?

92

Tengo el siguiente código:

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]

Básicamente, calculo un nuevo índice y lo uso para encontrar algún elemento de una lista. Para asegurarme de que el índice esté dentro de los límites de la lista, necesitaba escribir esas 2 ifdeclaraciones distribuidas en 4 líneas. Eso es bastante detallado, un poco feo ... Me atrevo a decir que es bastante poco pitónico .

¿Existe alguna otra solución más sencilla y compacta? (y más pitónico )

Sí, sé que puedo usar if elseen una línea, pero no se puede leer:

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

También sé que puedo encadenar max()y min()juntar. Es más compacto, pero siento que es un poco oscuro, más difícil de encontrar errores si lo escribo mal. En otras palabras, no lo encuentro muy sencillo.

new_index = max(0, min(new_index, len(mylist)-1))
Denilson Sá Maia
fuente
2
Si se siente "un poco oscuro", ¿convertirlo en una función?
Santa
1
Sí, puedo escribir una función, pero ese no es el punto. La pregunta es cómo implementar eso (ya sea en línea o en una función).
Denilson Sá Maia
clamp = lambda value, minv, maxv: max(min(value, maxv), minv)Usando la API de arma.sourceforge.net/docs.html#clamp
Dima Tisnek

Respuestas:

119

En realidad, esto es bastante claro. Mucha gente lo aprende rápidamente. Puede utilizar un comentario para ayudarlos.

new_index = max(0, min(new_index, len(mylist)-1))
S.Lott
fuente
12
Aunque siento que no es tan pitónico como debería ser, también creo que esta es la mejor solución que tenemos ahora.
Denilson Sá Maia
49
def clamp(n, smallest, largest): return max(smallest, min(n, largest))
CSL
3
@csl La gente siempre proporciona estas pequeñas funciones de ayuda, pero nunca sé dónde ponerlas. helperFunctions.py? ¿Un módulo separado? ¿Qué pasa si esto se llena de varias "funciones de ayuda" para cosas completamente diferentes?
Mateen Ulhaq
1
No lo sé, pero si recopila muchos de esos y los clasifica en módulos sensibles, ¿por qué no poner en GitHub y crear un paquete PyPi a partir de él? Probablemente se volvería popular.
CSL
@MateenUlhaqutils.py
Wouterr
85
sorted((minval, value, maxval))[1]

por ejemplo:

>>> 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
fuente
10
+1 para uso creativo de archivos sorted()integrados. Muy compacto, pero un poco oscuro. De todos modos, ¡siempre es bueno ver otras soluciones creativas!
Denilson Sá Maia
10
Muy creativo, y en realidad tan rápido como la min(max())construcción. Un poco más rápido en el caso de que el número esté en el rango y no se necesiten cambios.
poco todo el
40

muchas respuestas interesantes aquí, todas casi iguales, excepto ... ¿cuál es más rápida?

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 lo tiene !, usa python simple. La versión numpy es, quizás no sorprendentemente, la más lenta de todas. Probablemente porque está buscando matrices, donde las otras versiones simplemente ordenan sus argumentos.

SingleNegationElimination
fuente
7
@LenarHoyt no es tan sorprendente, considerando que el rendimiento de Numpy está diseñado en torno a matrices grandes, no a números únicos. Además, primero tiene que convertir el número entero en un tipo de datos interno y, dado que acepta varios tipos diferentes de entradas, probablemente lleve un tiempo considerable averiguar de qué tipo es la entrada y en qué convertirla. Verá un rendimiento de Numpy mucho mejor si lo alimenta con una matriz (preferiblemente no una lista o tupla, que tiene que convertir primero) de varios miles de valores.
blubberdiblub
Python es tres órdenes de magnitud más lento. 783 ns = 783.000 µs. Cometí el mismo error en el pasado. La notación es sutil.
Dustin Andrews
5
@DustinAndrews lo entendiste al revés. 1 µs es 10 ^ -6 segundos, 1 ns es 10 ^ -9 segundos. el ejemplo de Python completa 1 ciclo en 0,784 µs. O al menos, lo hizo en la máquina en la que lo probé. Este microbenchmark es tan útil como cualquier otro microbenchmark; puede alejarlo de ideas realmente malas, pero probablemente no lo ayudará mucho a encontrar la forma más rápida de escribir código útil .
SingleNegationElimination
Hay una ligera sobrecarga en la llamada de funciones. No he hecho los puntos de referencia, pero es muy posible mm_clipy py_clipserá igualmente rápido si usa el compilador JIT, como PyPy. Excepto que el primero es más legible, y la legibilidad es más importante en la filosofía de Python que una ligera ganancia de rendimiento la mayor parte del tiempo.
Highstaker
@DustinAndrews Le aconsejo que elimine su comentario objetivamente incorrecto porque lo obtuvo al revés.
Acumenus
38

Ver numpy.clip :

index = numpy.clip(index, 0, len(my_list) - 1)
Neil G
fuente
Los documentos dicen que el primer parámetro de clipes a, una "matriz que contiene elementos para recortar". Entonces tendrías que escribir numpy.clip([index], …, no numpy.clip(index, ….
Rory O'Kane
13
@ RoryO'Kane: ¿Lo intentaste?
Neil G
1
Pandas también permite esto en Series, DataFrames y Paneles.
Nour Wolf
17

Encadenar max()y min()juntar es el idioma normal que he visto. Si le resulta difícil de leer, escriba una función auxiliar para encapsular la operación:

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

¿Qué pasó con mi amado lenguaje Python legible? :-)

En serio, solo hazlo una función:

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

luego simplemente llámalo con algo como:

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

O una solución más simple y flexible en la que usted mismo hace el cálculo:

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

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

Si quisiera, incluso podría hacer que el mínimo / máximo sea una lista para que se vea más "matemáticamente puro":

x = restrict(val+7, [0, 42])
paxdiablo
fuente
6
Ponerlo en una función está bien (y aconseja, si lo estás haciendo mucho), pero creo miny maxson mucho más claro que un montón de condicionales. (No sé para qué addsirve, solo dilo clamp(val + 7, 0, 42).)
Glenn Maynard
1
@GlennMaynard. No estoy seguro de estar de acuerdo en que el mínimo y el máximo son más limpios. El objetivo de usarlos es poder colocar más en una línea, haciendo que el código sea menos legible.
Mad Physicist
10

Este me parece más pitónico:

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

Algunas pruebas:

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

Si su código parece demasiado difícil de manejar, una función podría ayudar:

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

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

Evite escribir funciones para tareas tan pequeñas, a menos que las aplique con frecuencia, ya que saturará su código.

para valores individuales:

min(clamp_max, max(clamp_min, value))

para listas de valores:

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