Comment fixer un entier à une certaine plage?

92

J'ai le code suivant:

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]

Fondamentalement, je calcule un nouvel index et l'utilise pour trouver un élément d'une liste. Afin de m'assurer que l'index est dans les limites de la liste, j'avais besoin d'écrire ces 2 ifinstructions réparties sur 4 lignes. C'est assez bavard, un peu moche ... Oserais-je dire, c'est assez antipythonique .

Existe-t-il une autre solution plus simple et plus compacte? (et plus pythonique )

Oui, je sais que je peux utiliser if elseen une seule ligne, mais ce n'est pas lisible:

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

Je sais aussi que je peux enchaîner max()et min()ensemble. C'est plus compact, mais je trouve que c'est un peu obscur, plus difficile de trouver des bugs si je tape mal. En d'autres termes, je ne trouve pas cela très simple.

new_index = max(0, min(new_index, len(mylist)-1))
Denilson Sá Maia
la source
2
Si cela semble "un peu obscur", en faire une fonction?
Santa
1
Ouais, je peux écrire une fonction, mais ce n'est pas le but. La question est de savoir comment implémenter cela (en ligne ou dans une fonction).
Denilson Sá Maia
clamp = lambda value, minv, maxv: max(min(value, maxv), minv)Utilisation de l'API d' arma.sourceforge.net
Dima Tisnek

Réponses:

119

C'est assez clair, en fait. Beaucoup de gens l'apprennent rapidement. Vous pouvez utiliser un commentaire pour les aider.

new_index = max(0, min(new_index, len(mylist)-1))
S.Lott
la source
12
Bien que je pense que ce n'est pas aussi pythonique qu'il devrait l'être, je pense aussi que c'est la meilleure solution que nous ayons actuellement.
Denilson Sá Maia
49
def clamp(n, smallest, largest): return max(smallest, min(n, largest))
csl le
3
@csl Les gens fournissent toujours ces petites fonctions d'assistance, mais je ne sais jamais où les mettre. helperFunctions.py? Un module séparé? Et si cela était jonché de diverses «fonctions d'aide» pour des choses complètement différentes?
Mateen Ulhaq
1
Je ne sais pas, mais si vous en collectez beaucoup et que vous les catégorisez en modules sensés, pourquoi ne pas mettre sur GitHub et en créer un package PyPi? Deviendrait probablement populaire.
csl
@MateenUlhaqutils.py
Wouterr
85
sorted((minval, value, maxval))[1]

par exemple:

>>> 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
la source
10
+1 pour l'utilisation créative de la fonction sorted()intégrée. Très compact, mais c'est juste un peu obscur. Quoi qu'il en soit, c'est toujours agréable de voir d'autres solutions créatives!
Denilson Sá Maia
10
Très créatif, et en fait à peu près aussi rapide que la min(max())construction. Très légèrement plus rapide dans le cas où le nombre est dans la plage et aucun échange n'est nécessaire.
quelque sorte
40

beaucoup de réponses intéressantes ici, toutes à peu près identiques, sauf ... laquelle est la plus rapide?

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 l' a!, utilise du python ordinaire. La version numpy est, peut-être sans surprise, la plus lente du lot. Probablement parce qu'il recherche des tableaux, où les autres versions ne font que commander leurs arguments.

SingleNegationElimination
la source
7
@LenarHoyt ce n'est pas si surprenant, étant donné que les performances de Numpy sont conçues autour de grands tableaux, pas de nombres uniques. De plus, il doit d'abord convertir l'entier en un type de données interne et comme il accepte plusieurs types d'entrées différents, il faut probablement beaucoup de temps pour déterminer en quel type l'entrée est et en quoi la convertir. Vous verrez de bien meilleures performances Numpy si vous lui alimentez un tableau (de préférence pas une liste ou un tuple, qu'il doit d'abord convertir) de plusieurs milliers de valeurs.
blubberdiblub
Python est trois ordres de grandeur plus lent. 783 ns = 783 000 µs. J'ai fait la même erreur dans le passé. La notation est subtile.
Dustin Andrews
5
@DustinAndrews vous avez ça à l'envers. 1 µs correspond à 10 ^ -6 secondes, 1 ns correspond à 10 ^ -9 secondes. l'exemple python complète 1 boucle en 0,784 µs. Ou du moins, sur la machine sur laquelle je l'ai testé. Ce microbenchmark est à peu près aussi utile que n'importe quel autre microbenchmark; cela peut vous éloigner des très mauvaises idées mais ne vous aidera probablement pas beaucoup à trouver le moyen le plus rapide d'écrire du code utile .
SingleNegationElimination
Il y a une légère surcharge sur l'appel des fonctions. Je n'ai pas fait les benchmarks, mais c'est tout à fait possible mm_clipet ce py_clipsera tout aussi rapide si vous utilisez le compilateur JIT, comme PyPy. Sauf que le premier est plus lisible et que la lisibilité est plus importante dans la philosophie de Python qu'un léger gain de performances la plupart du temps.
Highstaker
@DustinAndrews Je vous conseille de supprimer votre commentaire factuellement incorrect car vous l'avez fait à l'envers.
Acumenus
38

Voir numpy.clip :

index = numpy.clip(index, 0, len(my_list) - 1)
Neil G
la source
La documentation dit que le premier paramètre de clipest a, un «tableau contenant des éléments à découper». Vous auriez donc à écrire numpy.clip([index], …, non numpy.clip(index, ….
Rory O'Kane
13
@ RoryO'Kane: Avez-vous essayé?
Neil G
1
Pandas permet également cela sur les séries et les DataFrames et les panneaux.
Nour Wolf
17

L'enchaînement max()et min()ensemble est l'idiome normal que j'ai vu. Si vous avez du mal à lire, écrivez une fonction d'assistance pour encapsuler l'opération:

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

Qu'est-il arrivé à mon langage Python lisible bien-aimé? :-)

Sérieusement, faites-en une fonction:

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

alors appelez-le simplement avec quelque chose comme:

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

Ou une solution plus simple et plus flexible où vous faites vous-même le calcul:

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

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

Si vous le souhaitez, vous pouvez même créer une liste min / max pour qu'elle paraisse plus "mathématiquement pure":

x = restrict(val+7, [0, 42])
paxdiablo
la source
6
Le mettre dans une fonction est bien (et conseillé, si vous le faites beaucoup), mais je pense minet maxsont beaucoup plus clairs qu'un tas de conditions. (Je ne sais pas à quoi ça addsert - dis juste clamp(val + 7, 0, 42).)
Glenn Maynard
1
@GlennMaynard. Pas sûr que je puisse convenir que le min et le max sont plus propres. L'intérêt de les utiliser est de pouvoir en mettre plus sur une seule ligne, ce qui rend le code moins lisible.
Mad Physicist
10

Celui-ci me semble plus pythonique:

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

Quelques tests:

>>> clip(5, 2, 7)
5
>>> clip(1, 2, 7)
2
>>> clip(8, 2, 7)
7
Jens
la source
8

Si votre code semble trop lourd, une fonction peut vous aider:

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

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

Évitez d'écrire des fonctions pour des tâches aussi petites, sauf si vous les appliquez souvent, car cela encombrera votre code.

pour les valeurs individuelles:

min(clamp_max, max(clamp_min, value))

pour les listes de valeurs:

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