代码之家  ›  专栏  ›  技术社区  ›  Nicolas Dumazet

Pythonic方法实现三个类似的整数范围操作符?

  •  2
  • Nicolas Dumazet  · 技术社区  · 17 年前

    我正在研究一个循环问题。在这个问题中,我们把物体放在一个大小的环上 MAX

    我有三个简单的函数来测试范围包含。inRange(i,j,k)测试i是否在循环区间[j,k[(助记符为 i在范围(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的名字,但我想不出清晰的名字。有什么想法吗?

    7 回复  |  直到 17 年前
        1
  •  7
  •   Wesley    17 年前

    Zen of Python 我想到的原则是:

    • 简单总比复杂好。

    range

    range(start, end) 从中生成列表 start end . 该列表的第一个元素是 开始 end - 1 .

    没有 range_strict 功能或 inclusive_range 作用当我开始学习Python时,这对我来说非常尴尬。(“我只想从你那里得到一份清单 a b 包含全部费用这有多困难,Guido?”)然而,调用 范围

    正式建议

    您可能已经猜到了,我的建议是只创建一个函数来测试 在范围之内[ J , K ).事实上,我的建议是只保留现有的 inRange

    (由于您的问题特别提到Pythonicity,我建议您将函数命名为 in_range Python Style Guide

    正当理由

    为什么这是个好主意?

    • 单个函数很容易理解。学习如何使用它是很容易的。

      当然,对于您的三个启动函数中的每一个都可以这样说。到现在为止,一直都还不错。

    • 鉴于这三个函数的名称和行为相似,在某种程度上可能会使用错误的函数。除边缘情况外,函数返回相同的值,这一事实使情况更加复杂,这可能导致一个bug很难发现。通过只提供一个功能,您知道您不会犯这样的错误。

    • 该函数易于编辑。

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

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

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

      所以

      size = (k - j) % MAX
      

    警告

    使用此解决方案可能意味着对调用这些函数的代码进行相当多的重构。仔细查看这段代码,看看编辑它是非常困难还是单调乏味。


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

        2
  •  5
  •   Bite code    17 年前

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

        3
  •  4
  •   Nikhil    17 年前

    没有高阶函数,但代码更少,即使有额外的 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

        4
  •  2
  •   Nicolas Dumazet    17 年前

    现在我想到的是:

    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)
    

        5
  •  2
  •   John Machin Santi    17 年前

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

    1. 为参数使用有意义的名称: in_范围(x,lo,hi)是一个大的 与现有技术相比的改进 2击键成本。

    2. 记录以下事实: 约束高<马克斯的意思是 不可能表示以下范围: 包括 最大元素数。像 韦斯利说, 尺寸=(k-j)% 马克斯 大小=(高-低)%MAX 因此

        6
  •  1
  •   Kiv    17 年前

    为了让您的用户更熟悉它,我将使用一个与range()具有相同边界的main in_range函数。这使得它更容易记忆,并且有韦斯利提到的其他好特性。

    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)
    

        7
  •  1
  •   Tom Anderson    17 年前

    在模仿普通python的“范围内”习语方面,我比韦斯利更进一步;我会写一个循环的范围类:

    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
    

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

    在范围2中要做的是:

    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了!