【问题标题】:How to clamp an integer to some range?如何将整数限制在某个范围内?
【发布时间】:2021-12-02 20:43:18
【问题描述】:

我有以下代码:

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 行。这很冗长,有点难看...我敢说,这很un-pythonic。

还有其他更简单、更紧凑的解决方案吗?(以及更多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))

【问题讨论】:

  • 如果感觉“有点晦涩难懂”,就用它做一个函数?
  • 是的,我可以写一个函数,但这不是重点。问题是如何实现它(内联或在函数中)。
  • clamp = lambda value, minv, maxv: max(min(value, maxv), minv) 使用来自arma.sourceforge.net/docs.html#clamp的API

标签: python clamp


【解决方案1】:

实际上,这很清楚。很多人很快就学会了。您可以使用评论来帮助他们。

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

【讨论】:

  • 虽然我觉得它不像它应该的那样pythonic,但我也觉得这是我们现在最好的解决方案。
  • def clamp(n, smallest, largest): return max(smallest, min(n, largest))
  • @csl 人们总是提供这些小帮助函数,但我不知道把它们放在哪里。 helperFunctions.py?一个单独的模块?如果这被用于完全不同的事物的各种“辅助函数”乱扔怎么办?
  • 我不知道,但如果你收集了很多这些并将它们分类为合理的模块,为什么不放在 GitHub 上并从中创建一个 PyPi 包呢?可能会流行起来。
  • @MateenUlhaq utils.py
【解决方案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

【讨论】:

  • +1 用于创造性地使用内置的sorted()。非常紧凑,但它只是有点模糊。无论如何,看到其他创造性的解决方案总是很高兴!
  • 非常有创意,实际上和min(max()) 构造一样快。在数字在范围内并且不需要交换的情况下,速度会稍微快一些。
【解决方案3】:

这里有很多有趣的答案,都差不多,除了...哪个更快?

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 版本可能是其中最慢的版本,这也许不足为奇。可能是因为它正在寻找数组,而其他版本只是对它们的参数进行排序。

【讨论】:

  • @LenarHoyt 考虑到 Numpy 的性能是围绕大型数组而不是单个数字设计的,这并不奇怪。此外,它必须首先将整数转换为内部数据类型,并且由于它接受几种不同类型的输入,因此可能需要相当长的时间来确定输入的类型以及将其转换为什么类型。如果你给它一个包含数千个值的数组(最好不是列表或元组,它必须首先转换),你会看到更好的 Numpy 性能。
  • @DustinAndrews 你搞错了。 1 µs 是 10^-6 秒,1 ns 是 10^-9 秒。 python 示例在 0.784 µs 内完成 1 个循环。或者至少,它在我测试过的机器上确实如此。这个微基准与任何其他微基准一样有用。它可以让你远离真正糟糕的想法,但可能不会帮助你找到真正最快的方法来编写有用的代码。
  • 函数调用有一点开销。我还没有做基准测试,但是如果你使用 JIT 编译器,比如 PyPy,mm_clip 和 py_clip 很可能会同样快。除了前者更具可读性,而且在 Python 的哲学中,可读性在大多数情况下比轻微的性能提升更重要。
【解决方案4】:

见numpy.clip:

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

【讨论】:

  • The docs 说clip 的第一个参数是a,一个“包含要剪辑元素的数组”。所以你必须写numpy.clip([index], …,而不是numpy.clip(index, …。
  • @RoryO'Kane:你试过了吗?
  • Pandas 也允许在 Series 和 DataFrames 和 Panels 上这样做。
【解决方案5】:

将max() 和min() 链接在一起是我见过的常见习语。如果你觉得阅读困难,写一个辅助函数来封装操作:

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

【讨论】:

    【解决方案6】:

    我心爱的可读 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)
    

    如果您愿意,您甚至可以将最小值/最大值设为一个列表,使其看起来更“数学纯”:

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

    【讨论】:

    • 把它放在一个函数中很好(建议,如果你经常这样做),但我认为min 和max 比一堆条件更清楚。 (我不知道add 是干什么用的——就说clamp(val + 7, 0, 42)。)
    • @GlennMaynard。不确定我是否同意 min 和 max 更干净。使用它们的全部意义在于能够在一行中添加更多内容,从而有效地降低代码的可读性。
    【解决方案7】:

    这对我来说似乎更蟒蛇:

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

      【讨论】:

        【解决方案9】:

        避免为这样的小任务编写函数,除非你经常应用它们,因为它会使你的代码变得混乱。

        对于单个值:

        min(clamp_max, max(clamp_min, value))
        

        对于值列表:

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

        【讨论】:

        • 我不同意“避免为这样的小任务编写函数”,这种建议会导致函数长达 1k 行。命名一个概念并封装它有它的好处。
        猜你喜欢
        • 1970-01-01
        • 2017-05-22
        • 2015-04-06
        • 1970-01-01
        • 2014-09-21
        • 2014-08-04
        • 1970-01-01
        • 2017-10-10
        相关资源
        最近更新 更多