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

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

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 строкам. Это довольно многословие, немного уродливое... Смею сказать, это совершенно непитоноязычное.

Есть ли еще более простое и компактное решение? (и более pythonic)

Да, я знаю, что я могу использовать 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))

Ответ 1

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

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

Ответ 2

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

Ответ 3

См. numpy.clip:

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

Ответ 4

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

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

Ответ 5

Что случилось с моим любимым читаемым языком 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])

Ответ 6

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

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

Ответ 7

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

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

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

Ответ 8

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

для отдельных значений:

min(clamp_max, max(clamp_min, value))

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

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

Ответ 9

Это кажется мне более pythonic:

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