Як затиснути ціле число на деякий діапазон?


92

У мене є такий код:

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
Dima Tisnek 01

Відповіді:


119

Насправді це цілком зрозуміло. Багато людей це швидко засвоюють. Ви можете використати коментар, щоб допомогти їм.

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

12
Хоча я відчуваю, що це не настільки пітонічно, як повинно бути, я також вважаю, що це найкраще рішення, яке ми маємо зараз.
Denilson Sá Maia

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

3
@csl Folks завжди надає ці невеликі допоміжні функції, але я ніколи не знаю, куди їх покласти. helperFunctions.py? Окремий модуль? Що, якщо це буде завалено різними "допоміжними функціями" для абсолютно різних речей?
Mateen Ulhaq,

1
Не знаю, але якщо ви збираєте багато таких і класифікуєте їх на розумні модулі, чому б не покласти на GitHub і не створити з нього пакет PyPi? Напевно, став би популярним.
csl

@MateenUlhaqutils.py
Wouterr

85
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

10
+1 для творчого використання sorted()вбудованого. Дуже компактний, але трохи трохи неясний. У будь-якому випадку, завжди приємно бачити інші креативні рішення!
Denilson Sá Maia

10
Дуже креативно, і насправді приблизно так само швидко, як і min(max())будівництво. Дуже трохи швидше, якщо число знаходиться в діапазоні, і заміни не потрібні.
kindall

40

тут багато цікавих відповідей, приблизно однакових, за винятком ... який швидший?

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 має !, використовуйте звичайний ol 'python. Версія Numpy, мабуть, не дивно, найповільніша з партії. Можливо тому, що він шукає масиви, де інші версії просто впорядковують свої аргументи.


7
@LenarHoyt це не так дивно, враховуючи, що продуктивність Numpy розроблена навколо великих масивів, а не окремих чисел. Крім того, йому спочатку потрібно перетворити ціле число на внутрішній тип даних, і оскільки він приймає декілька різних типів входів, ймовірно, потрібен значний час, щоб з’ясувати, якого типу є вхід і в який його конвертувати. Ви побачите набагато кращу продуктивність Numpy, якщо подасте в неї масив (бажано не список чи кортеж, який він повинен перетворити першим) із декількох тисяч значень.
blubberdiblub

Python повільніший на три порядки. 783 нс = 783 000 мкс. Я робив ту ж помилку в минулому. Позначення тонке.
Дастін Ендрюс

5
@DustinAndrews у вас це назад. 1 мкс - це 10 ^ -6 секунд, 1 нс - 10 ^ -9 секунд. приклад python завершує 1 цикл за 0,784 мкс. Або, принаймні, це сталося на машині, на якій я його тестував. Цей мікровідповідний інструмент є настільки ж корисним, як і будь-який інший мікровідповідний знак; це може спрямувати вас від справді поганих ідей, але, мабуть, не надто допоможе вам знайти фактично найшвидший спосіб написати корисний код.
SingleNegationElimination

Виклик функцій має незначні накладні витрати. Я не зробив контрольні показники, але це цілком можливо , що mm_clipі py_clipбуде однаково швидко , якщо ви використовуєте JIT компілятор, як PyPy. За винятком того, що перший є більш читабельним, а читабельність є важливішою у філософії Python, ніж незначний приріст продуктивності більшу частину часу.
Highstaker

@DustinAndrews Я раджу видалити ваш фактично неправильний коментар, тому що ви отримали його назад.
Проникність

38

Див. Numpy.clip :

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

Документи кажуть, що першим параметром clipis aє "масив, що містить елементи для відсікання". Тож вам довелося б писати numpy.clip([index], …, а не numpy.clip(index, ….
Рорі О'Кейн,

13
@ RoryO'Kane: Ти спробував?
Neil G

1
Pandas також дозволяє це використовувати на Series і DataFrames, а також на панелях.
Nour Wolf

17

Зв’язування ланцюгів 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])

6
Збираю в функції нормально (і рекомендується, якщо ви робите це багато), але я думаю , що minі maxнабагато ясніше , ніж купа умовних. (Я не знаю, для чого addце - просто скажіть clamp(val + 7, 0, 42).)
Гленн Мейнард,

1
@GlennMaynard. Не впевнений, що я можу погодитися, що min та max є чистішими. Вся суть їх використання полягає в тому, щоб мати можливість набивати більше в один рядок, ефективно роблячи код менш розбірливим.
Божевільний фізик

10

Мені це здається більш пітонічним:

>>> 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)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.