¿Cómo puedo sujetar (recortar, restringir) un número a algún rango?

7 minutos de lectura

Avatar de usuario de Denilson Sá Maia
Denilson Sá Maia

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 esos 2 if declaraciones distribuidas en 4 líneas. Eso es bastante detallado, un poco feo… Me atrevo a decir que es bastante no pitónico.

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

Sí, sé que puedo usar if else en una línea, pero no es legible:

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() juntos. Es más compacto, pero siento que es un poco oscuro, más difícil encontrar errores si lo escribo mal. En otras palabras, no lo encuentro muy sencillo.

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

¿Vea la forma Pythonic de reemplazar los valores de la lista con un límite superior e inferior (sujeción, recorte, umbral)? para una técnica específica para procesar valores en una matriz Numpy.

  • Si se siente “un poco oscuro”, ¿hacer una función de eso?

    – Papa Noel

    3 de noviembre de 2010 a las 23:40

  • 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

    5 de noviembre de 2010 a las 12:15

  • clamp = lambda value, minv, maxv: max(min(value, maxv), minv) Usando la API de arma.sourceforge.net/docs.html#clamp

    – Dima Tisnek

    1 de diciembre de 2015 a las 8:41

Esto es bastante claro, en realidad. Muchas personas lo aprenden rápidamente. Puedes usar un comentario para ayudarlos.

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

  • 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

    13 de noviembre de 2010 a las 1:14

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

    – csl

    21 de septiembre de 2015 a las 11:05

  • @csl La gente siempre proporciona estas pequeñas funciones auxiliares, pero nunca sé dónde colocarlas. helperFunctions.py? ¿Un módulo aparte? ¿Qué pasa si esto se llena de varias “funciones auxiliares” para cosas completamente diferentes?

    –Mateen Ulhaq

    25 de mayo de 2017 a las 2:01

  • No lo sé, pero si recopila muchos de esos y los clasifica en módulos sensibles, ¿por qué no poner GitHub y crear un paquete PyPi a partir de él? Probablemente se volvería popular.

    – csl

    25 mayo 2017 a las 11:25

  • @MateenUlhaq utils.py

    – Wouterr

    6 de agosto de 2020 a las 15:09

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

  • +1 por el uso creativo de sorted() incorporado. Muy compacto, pero es un poco oscuro. De todos modos, ¡siempre es bueno ver otras soluciones creativas!

    – Denilson Sá Maia

    4 de noviembre de 2010 a las 0:06


  • Muy creativo, y en realidad casi tan rápido como el min(max()) construcción. Ligeramente más rápido en el caso de que el número esté dentro del rango y no se necesiten intercambios.

    – tipo todo

    4 de noviembre de 2010 a las 0:35

Avatar de usuario de SingleNegationElimination
Eliminación de negación única

muchas respuestas interesantes aquí, todas más o menos 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 solo ordenan sus argumentos.

  • @LenarHoyt no es tan sorprendente, teniendo en cuenta que el rendimiento de Numpy está diseñado en torno a grandes matrices, no a números únicos. Además, primero tiene que convertir el número entero a 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

    26 de enero de 2017 a las 21:34


  • @DustinAndrews lo entendiste al revés. 1 µs es 10^-6 segundos, 1 ns es 10^-9 segundos. el ejemplo de python completa 1 bucle en 0,784 µs. O al menos, lo hizo en la máquina en la que lo probé. Este micropunto de referencia es tan útil como cualquier otro micropunto de referencia; puede alejarlo de ideas realmente malas, pero probablemente no lo ayudará mucho a encontrar la forma más rápida de escribir útil código.

    – Eliminación de negación simple

    3 de enero de 2018 a las 22:03


  • Hay una ligera sobrecarga en la llamada de funciones. No he hecho los puntos de referencia, pero es muy posible que mm_clip y py_clip será 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 mejora en el rendimiento la mayor parte del tiempo.

    – Highstaker

    19 de septiembre de 2018 a las 8:36


Avatar de usuario de Neil G
neil g

Ver numpy.clip:

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

avatar de usuario de paxdiablo
paxdiablo

¿Qué pasó con mi amado lenguaje Python legible? 🙂

En serio, solo conviértelo en una función:

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

entonces 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 una lista de min/max para que se vea más “matemáticamente pura”:

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

  • Ponerlo en una función está bien (y se recomienda, si lo está haciendo mucho), pero creo min y max son mucho más claros que un montón de condicionales. (no sé qué add es para – solo di clamp(val + 7, 0, 42).)

    –Glenn Maynard

    3 de noviembre de 2010 a las 23:40

  • @GlennMaynard. No estoy seguro de poder estar de acuerdo en que min y max son más limpios. El objetivo de usarlos es poder incluir más en una línea, lo que hace que el código sea menos legible.

    – Físico loco

    1 de agosto de 2018 a las 14:25

Avatar de usuario de Laurence Gonsalves
Laurence Gonsalves

Encadenamiento max() y min() juntos 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))

  • Ponerlo en una función está bien (y se recomienda, si lo está haciendo mucho), pero creo min y max son mucho más claros que un montón de condicionales. (no sé qué add es para – solo di clamp(val + 7, 0, 42).)

    –Glenn Maynard

    3 de noviembre de 2010 a las 23:40

  • @GlennMaynard. No estoy seguro de poder estar de acuerdo en que min y max son más limpios. El objetivo de usarlos es poder incluir más en una línea, lo que hace que el código sea menos legible.

    – Físico loco

    1 de agosto de 2018 a las 14:25

Avatar de usuario de Jens
Jens

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

¿Ha sido útil esta solución?