Как зажать целое число в некотором диапазоне?


у меня есть следующий код:

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]

в принципе, я вычисляю новый индекс и использую его, чтобы найти какой-то элемент из списка. Чтобы убедиться, что индекс находится внутри границ списка, мне нужно было написать эти 2 if заявления разбросаны на 4 строки. Это довольно многословно, немного некрасиво... Осмелюсь сказать, это довольно ООН-подходящие для Python.

есть ли другое более простое и компактное решение? (и больше весть)

Да, я знаю, что могу использовать if else в одной строке, но это не читается:

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

Я также знаю, что я могу цепи max() и min() вместе. Он более компактный, но я чувствую, что это немного неясно, сложнее найти ошибки, если я наберу его неправильно. Другими словами, Я не нахожу это очень простым.

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

9 ответов:

Это понятно, на самом деле. Многие люди быстро учатся этому. Вы можете использовать комментарий, чтобы помочь им.

new_index = max(0, min(new_index, len(mylist)-1))
sorted((minval, value, maxval))[1]

например:

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

посмотреть numpy.клип:

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

много интересных ответов здесь, все о том же, за исключением... кто быстрее?

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 есть!, используйте простой старый питон. Версия numpy, возможно, не удивительно, самая медленная из всех. Вероятно, потому что он ищет массивы, где другие версии просто упорядочивают свои аргументы.

сцепление max() и min() вместе-это нормальная идиома, которую я видел. Если вам трудно читать, напишите вспомогательную функцию для инкапсуляции операции:

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

что случилось с моим любимым читаемым языком Python? : -)

серьезно, просто сделайте это функцией:

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

тогда просто позвоните ему с чем-то вроде:

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

или более простое, более гибкое решение, где вы делаете расчет самостоятельно:

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

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

если бы вы хотели, вы могли бы даже сделать список min / max, чтобы он выглядел более "математически чистым":

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

Если ваш код кажется слишком громоздким, функция может помочь:

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

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

Это, кажется, более подходящие для Python мне:

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

несколько тестов:

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

избегайте написания функций для таких небольших задач, если вы не применяете их часто, так как это будет загромождать ваш код.

для индивидуальных значений:

min(clamp_max, max(clamp_min, value))

для списков значений:

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