Pythonic方式实现三个相似的整数范围运算符?

时间:2009-06-12 06:48:02

标签: python operators

我正在处理一个循环问题。在这个问题中,我们将对象放在大小为MAX的环上,并从(0到MAX-1)分配ID。

我有三个简单的函数来测试范围包含。 inRange(i,j,k)测试i是否在循环区间[j,k [(助记符是 i inRange(j,k))中。并且我对范围有相同的] j,k [和] j,k]。

这三种方法中的代码看起来从一种方法复制到另一种方法:

def inRange(i,j,k):
    """
    Returns True if i in [j, k[
    * 0 <= i, j, k < MAX
    * no order is assumed between j and k: we can have k < j
    """
    if j <= k:
        return j <= i < k
    # j > k :
    return j <= i or i < k

def inStrictRange(i,j,k):
    """
    Returns True if i in ]j, k[
    * 0 <= i, j, k < MAX
    * no order is assumed between j and k: we can have k < j
    """
    if j <= k:
        return j < i < k
    # j > k :
    return j < i or i < k

def inRange2(i,j,k):
    """
    Returns True if i in ]j, k]
    * 0 <= i, j, k < MAX
    * no order is assumed between j and k: we can have k < j
    """
    if j <= k:
        return j < i <= k
    # j > k :
    return j < i or i <= k

您是否知道实施这三种方法的更简洁方法?毕竟,只有运营商正在改变?!

在考虑了更好的解决方案之后,我提出了:

from operator import lt, le
def _compare(i,j,k, op1, op2):
    if j <= k:
        return op1(j,i) and op2(i,k)
    return op1(j,i) or op2(i,k)

def inRange(i,j,k):
    return _compare(i,j,k, le, lt)
def inStrictRange(i,j,k):
    return _compare(i,j,k, lt, lt)
def inRange2(i,j,k):
    return _compare(i,j,k, lt, le)

还好吗?你能想出更直观的东西吗? 简而言之,编写这三个运算符的Pythonic方法是什么?

另外,我讨厌inRange,inStrictRange,inRange2这个名字,但我想不出清楚的名字。有什么想法吗?

感谢。

7 个答案:

答案 0 :(得分:7)

两个Zen of Python原则跳跃于脑海:

  • 简单比复杂更好。
  • 应该有一种 - 最好只有一种 - 显而易见的方法。

range

Python内置函数range(start, end)生成一个从startend的列表。 1 该列表的第一个元素是start ,最后一个元素是end - 1

没有range_strict函数或inclusive_range函数。当我开始使用Python时,这对我来说非常尴尬。 (“我只想要一个从ab的列表!这有多难,Guido?”)但是,调用range函数时使用的约定很简单,也很容易记住并且缺少多个功能使得每次都能够很容易地记住如何生成范围。

建议

正如您可能已经猜到的,我的建议是仅创建一个函数来测试 i 是否在[ j k 的范围内>)。实际上,我的建议是仅保留现有的inRange功能。

(由于您的问题专门提到了Pythonicity,我建议您将该函数命名为in_range以更好地适应Python Style Guide。)

理由

为什么这是一个好主意?

  • 单一功能易于理解。学习如何使用它非常容易。

    当然,对于你的三个起始功能中的每一个都可以这样说。到目前为止一切顺利。

  • 只有一个功能需要学习。没有三个函数具有必然相似的名称。

    鉴于您的三个函数的名称和行为相似,您可能会在某些时候使用错误的函数。事实上,除了边缘情况之外,函数返回相同的值,这可能导致难以找到的逐个错误。只使一个功能可用,你知道你不会犯这样的错误。

  • 该功能易于编辑。

    您不太可能需要调试或编辑这么简单的代码。但是,如果您需要这样做,则只需编辑此功能。使用原来的三个功能,您必须在三个位置进行相同的编辑。通过自行回答中修改后的代码,操作员混淆后代码的直观性会稍差。

  • 范围的“大小”是显而易见的。

    对于您将使用inRange(i, j, k)的给定环,很明显范围[ j k )将涵盖多少元素。这是代码。

    if j <= k:
        size = k - j
    if j > k:
        size = k - j + MAX
    

    因此

    size = (k - j) % MAX
    

注意事项

我从一个完全通用的角度来看待这个问题,例如为一个公开发布的库编写函数的人。由于我不知道您的问题域,我不能说这是否是一个实用的解决方案。

使用此解决方案可能意味着对调用这些函数的代码进行相当多的重构。查看此代码,看看编辑是否过于困难或乏味。


1 :实际上,它是range([start], end, [step])。我相信你明白我的意思。

答案 1 :(得分:5)

Pythonic的方法是选择可读性,因此保留3种方法,就像它们在开始时一样。

它们不是巨大的方法,或者有数千种方法,或者你必须动态生成它们。

答案 2 :(得分:4)

没有高阶函数,但代码更少,即使有无关的else

def exclusive(i, j, k):
    if j <= k:
        return j < i < k
    else:
        return j < i or i < k

def inclusive_left(i, j, k):
    return i==j or exclusive(i, j, k)

def inclusive_right(i, j, k):
    return i==k or exclusive(i, j, k)

我实际上尝试将标识符切换为n, a, b,但代码开始看起来不那么紧密。 (我的观点:完善这些代码可能无法充分利用时间。)

答案 3 :(得分:2)

现在我想的是:

def comparator(lop, rop):
    def comp(i, j, k):
        if j <= k:
            return lop(j, i) and rop(i,k)
        return lop(j, i) or rop(i,k)

    return comp

from operator import le, lt

inRange = comparator(le, lt)
inStrictRange = comparator(lt, lt)
inRange2 = comparator(lt, le)

确实看起来更好。

答案 4 :(得分:2)

我当然同意你只需要一个函数,并且函数应该使用(Pythonic)半开放范围。

两个建议:

  1. 为args使用有意义的名称: in_range(x,lo,hi)很大 改善相对于 2键击成本。

  2. 记录事实 约束hi&lt; MAX表示它是 不可能表达一个范围 包括所有 MAX元素。如 韦斯利说, size =(k - j)% MAX size =(hi-lo)%MAX 因此 0 <= size&lt; MAX

答案 5 :(得分:1)

为了让用户更熟悉,我会有一个主要的in_range函数,其范围与range()相同。这使得它更容易记住,并且具有Wesley提到的其他不错的属性。

def in_range(i, j, k):
    return (j <= i < k) if j <= k else (j <= i or i < k)

通过在j和/或k中添加1,您当然可以单独使用此一个用于所有用例。如果您发现您经常使用特定表单,那么您可以根据主要表单来定义它:

def exclusive(i, j, k):
    """Excludes both endpoints."""
    return in_range(i, j + 1, k)

def inclusive(i, j, k):
    """Includes both endpoints."""
    return in_range(i, j, k + 1)

def weird(i, j, k):
    """Excludes the left endpoint but includes the right endpoint."""
    return in_range(i, j + 1, k + 1)

这比使用操作员的时间要短,而且理解起来也不那么容易混淆。另请注意,您应该在Python中使用下划线而不是camelCase作为函数名称。

答案 6 :(得分:1)

我要比韦斯利更进一步地解决正常的蟒蛇'范围'成语;我写了一个cyclic_range类:

import itertools

MAX = 10 # or whatever

class cyclic_range(object):
    def __init__(self, start, stop):
        # mod so you can be a bit sloppy with indices, plus -1 means the last element, as with list indices
        self.start = start % MAX
        self.stop = stop % MAX
    def __len__(self):
        return (self.stop - self.start) % MAX
    def __getitem__(self, i):
        return (self.start + i) % MAX
    def __contains__(self, x):
        if (self.start < self.stop):
            return (x >= self.start) and (x < self.stop)
        else:
            return (x >= self.start) or (x < self.stop)
    def __iter__(self):
        for i in xrange(len(self)):
            yield self[i]
    def __eq__(self, other):
        if (len(self) != len(other)): return False
        for a, b in itertools.izip(self, other):
            if (a != b): return False
        return True
    def __hash__(self):
        return (self.start << 1) + self.stop
    def __str__(self):
        return str(list(self))
    def __repr__(self):
        return "cyclic_range(" + str(self.start) + ", " + str(self.stop) + ")"
    # and whatever other list-like methods you fancy

然后您可以编写如下代码:

if (myIndex in cyclic_range(firstNode, stopNode)):
    blah

相当于inRange。要做inStrictRange,请写:

if (myIndex in cyclic_range(firstNode + 1, stopNode)):

要做inRange2:

if (myIndex in cyclic_range(firstNode + 1, stopNode + 1)):

如果您不喜欢手动添加,请添加以下方法:

    def strict(self):
        return cyclic_range(self.start + 1, self.stop)
    def right_closed(self):
        return cyclic_range(self.start + 1, self.stop + 1)

然后做:

if (myIndex in cyclic_range(firstNode, stopNode).strict()): # inStrictRange
if (myIndex in cyclic_range(firstNode, stopNode).closed_right()): # inRange2

虽然这种方法是,恕我直言,更具可读性,它确实涉及进行分配,而不仅仅是函数调用,这更昂贵 - 尽管仍然是O(1)。但是如果你真的关心性能,你就不会使用python!