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

94

У меня такой код:

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

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

Да, я знаю, что могу использовать 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))
Денилсон Са Майя
источник
2
Если он кажется "непонятным", сделать из него функцию?
Санта
1
Да, я могу написать функцию, но дело не в этом. Вопрос в том, как это реализовать (встроенным или в функции).
Denilson Sá Maia
clamp = lambda value, minv, maxv: max(min(value, maxv), minv)Использование API из arma.sourceforge.net/docs.html#clamp
Дима Тиснек

Ответы:

121

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

new_index = max(0, min(new_index, len(mylist)-1))
С.Лотт
источник
12
Хотя я считаю, что это не так питонично, как должно быть, я также считаю, что это лучшее решение, которое у нас есть сейчас.
Denilson Sá Maia
50
def clamp(n, smallest, largest): return max(smallest, min(n, largest))
csl
3
@csl Folks всегда предоставляет эти небольшие вспомогательные функции, но я никогда не знаю, где их разместить. helperFunctions.py? Отдельный модуль? Что, если здесь будет множество «вспомогательных функций» для совершенно разных вещей?
Матин Улхак
2
Я не знаю, но если вы соберете много из них и разделите их на разумные модули, почему бы не поставить GitHub и не создать из него пакет PyPi? Вероятно, стал бы популярным.
csl
@MateenUlhaqutils.py
Wouterr
90
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
Джон Ла Рой
источник
12
+1 за творческое использование sorted()встроенного. Очень компактный, но немного непонятный. В любом случае, всегда приятно видеть другие креативные решения!
Denilson Sá Maia
12
Очень креативно и практически так же быстро, как и min(max())строительство. Немного быстрее в том случае, если число находится в диапазоне и перестановки не требуются.
kindall
41

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

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

SingleNegationElimination
источник
7
@LenarHoyt это неудивительно, учитывая, что производительность Numpy рассчитана на большие массивы, а не на отдельные числа. Кроме того, он должен сначала преобразовать целое число во внутренний тип данных, и, поскольку он принимает несколько различных типов входных данных, вероятно, потребуется значительное время, чтобы выяснить, какой тип входных данных и во что его преобразовать. Вы увидите гораздо лучшую производительность Numpy, если передадите ему массив (желательно не список или кортеж, который он должен преобразовать первым) из нескольких тысяч значений.
blubberdiblub
Python на три порядка медленнее. 783 нс = 783 000 мкс. Я делал ту же ошибку в прошлом. Обозначения тонкие.
Дастин Эндрюс
7
@DustinAndrews, у тебя все наоборот. 1 мкс составляет 10 ^ -6 секунд, 1 нс - 10 ^ -9 секунд. пример python завершает 1 цикл за 0,784 мкс. Или, по крайней мере, на машине, на которой я это тестировал. Этот микротест примерно так же полезен, как и любой другой микротест; он может отвлечь вас от действительно плохих идей, но, вероятно, не сильно поможет вам найти действительно самый быстрый способ написать полезный код.
SingleNegationElimination 03
При вызове функций возникают небольшие накладные расходы. Я не проводил тестов, но вполне возможно, что mm_clipи py_clipбудет так же быстро, если вы используете JIT-компилятор, например PyPy. За исключением того, что первое более читабельно, а читаемость в философии Python более важна, чем небольшое повышение производительности в большинстве случаев.
Highstaker
@DustinAndrews Я советую удалить ваш фактически неверный комментарий, потому что вы его получили наоборот.
Acumenus
38

См. Numpy.clip :

index = numpy.clip(index, 0, len(my_list) - 1)
Нил Джи
источник
В документах говорится, что первым параметром clipявляется a«массив, содержащий элементы для обрезки». Так что писать придется numpy.clip([index], …, а не numpy.clip(index, ….
Рори О'Кейн,
13
@ RoryO'Kane: Вы пробовали?
Neil G
1
Pandas также позволяет это в сериях, фреймах данных и панелях.
Nour Wolf
19

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

def clamp(minimum, x, maximum):
    return max(minimum, min(x, maximum))
Лоуренс Гонсалвес
источник
14

Что случилось с моим любимым читаемым языком 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])
Paxdiablo
источник
6
Включение его в функцию - это нормально (и рекомендуется, если вы делаете это много), но я думаю, minи maxэто намного понятнее, чем связка условных выражений. (Я не знаю, для чего add- просто скажите clamp(val + 7, 0, 42).)
Гленн Мейнард,
1
@GlennMaynard. Не уверен, что могу согласиться с тем, что min и max чище. Весь смысл их использования в том, чтобы иметь возможность разместить больше в одной строке, эффективно делая код менее разборчивым.
Безумный физик
11

Мне этот кажется более питоническим:

>>> 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
Йенс
источник
8

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

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

new_index = clamp(0, new_index, len(mylist)-1)
Грег Хьюгилл
источник
2

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

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

min(clamp_max, max(clamp_min, value))

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

map(lambda x: min(clamp_max, max(clamp_min, x)), values)
Йетце Шаафсма
источник