【问题标题】:Pythonic way to implement three similar integer range operators?Pythonic 方式来实现三个类似的整数范围运算符?
【发布时间】:2009-06-12 06:48:02
【问题描述】:

我正在处理一个循环问题。在这个问题中,我们将对象放在大小为 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)

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

另外,我讨厌 inRange、inStrictRange、inRange2 名称,但我想不出清晰的名称。有什么想法吗?

谢谢。

【问题讨论】:

    标签: python operators


    【解决方案1】:

    两个Zen of Python 原则跃入脑海:

    • 简单胜于复杂。
    • 应该有一种——最好只有一种——明显的方法。

    range

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

    没有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])。我相信你明白我的意思。

    【讨论】:

    • 哇。感谢您花时间写这个答案。但是 afaik, range() 生成一个列表。而且我真的不想为每个通用比较生成一个列表:我们正在讨论生成一个大小为 O(n) 的列表,并执行 O(n) 操作 (contains_) 而不是我的三个算术运算。这是一个非常重要的开销:)
    • 我并不是说你应该为你的比较生成一个范围。我是说你应该考虑为什么只有一个range 函数,然后考虑只写一个inRange 函数,原因相同。您的 inRange 函数显然是 O(1)。但是,您应该考虑完全省略其他两个函数。如果你突然需要一个包含在 ab 之间的 xinRange,你有 inRange(x, a, b+1),这也是 O(1) .
    • 对,抱歉,您的回答读得太快了。那我当然同意你的建议。 in_range2(x, lo, hi) 可以通过not in_range(x, hi, lo)获得
    • 为了清楚起见,我将对in_range2(x, lo, hi) 的调用重写为in_range(x, lo + 1, hi + 1)。毕竟,pythonic 的目标是易于理解。诚然,在边界上加一个也不是很清楚,但是用时间和铅笔来计算会更容易一些。
    • 如果您不想生成列表,请改用 xrange()。它使用 yield stackoverflow.com/questions/231767/…) 并动态生成值。
    【解决方案2】:

    Pythonic 的做法是选择可读性,因此保持 3 个方法与开始时一样。

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

    【讨论】:

    • 也许吧。但我的问题有点乞求另一种方法,或者至少是建议:)
    【解决方案3】:

    没有高阶函数,但代码更少,即使是无关的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,但代码开始看起来不那么连贯了。 (我的观点:完善这段代码可能不会有效地利用时间。)

    【讨论】:

    • 我想我喜欢这个:专注于数学意义而不是代码分解。和 /yes/*3 生产力评论:)
    【解决方案4】:

    现在我在想这样的事情:

    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)
    

    确实看起来更好。

    【讨论】:

    • 这确实是个好方法。我会推荐名称“比较器”或类似名称; “比较器”表示该功能是一个比较的设备,而不仅仅是比较的东西。小事,我知道=)
    • 确实很优雅。不幸的是,le 和 lt 并没有像操作符文字那样跳出来。
    【解决方案5】:

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

    两个建议:

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

    2. 记录以下事实: 约束 hi 所有个 MAX 元素。作为 韦斯利说,size = (k - j) % MAXsize = (hi - lo) % MAX 因此 0 .

    【讨论】:

    • 两个非常有用的点,谢谢。对于大多数答案,大小为0的范围也存在问题。但幸运的是,这不在我今天的范围内:)
    【解决方案6】:

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

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

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

    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)
    

    这比使用运算符更短,也更容易理解。另外,请注意,您应该使用下划线而不是 camelCase 作为 Python 中的函数名称。

    【讨论】:

    • +1 表示 CamelCase 备注,并提醒我对于自然“i in [lo, hi[” 'i in [lo, hi+1]”。不过有点不完美:您需要包含 MAX 模,因为我的范围是循环的 :)
    【解决方案7】:

    在模仿正常的 python 'in range' 成语方面,我会比 Wesley 更进一步;我会写一个 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)):
    

    在Range2中做:

    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!

    【讨论】:

    • 在哲学方面,我不同意“如果你真的关心性能,你就不会使用 python!”。我喜欢 Python 的可读性。原型实现和改进算法(算法,而不是实现)非常棒。然后,如果您需要快速的东西,将其移植到另一种语言并不难。
    • 也很有趣,谢谢 :) 一个小评论:hash 很快,但非常合谋:约 70% 的冲突。这可能是不需要的:)
    猜你喜欢
    • 1970-01-01
    • 2013-05-24
    • 1970-01-01
    • 1970-01-01
    • 2013-06-13
    • 2013-08-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多