将值范围表示为Python序列类型 [英] Representing a Range of values as a Python Sequence Type

查看:243
本文介绍了将值范围表示为Python序列类型的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有兴趣代表一个范围,类似于Guava的 Range 键入。具体来说,它应该有一个开始和结束,并且表示两者之间的所有值(作为第一遍,我只是代表规范的开放范围,即 [5,10),但任何开/关范围的正确表示将是一个合理的功能)。

I'm interested in representing a range, similar to Guava's Range type, in Python. Specifically, it should have a start and end, and represent all values between the two (as a first pass, I'm fine with only representing the canonical open-closed range, i.e. [5,10), but proper representation of any open/closed range would be a reasonable feature).

我知道 range() 内置,但我' m希望支持任意类型(或具体日期,对于我的用例)。

I know about the range() builtin, but I'm hoping to support arbitrary types (or specifically dates, for my use case).

看Python的 type hierarchy ,似乎有一个范围可以是 Sequence Set 非常逻辑地键入,但我不确定哪一个更有意义,如果最好放弃我的班级进入该层次结构,并简单地实现我想要的行为

Looking at Python's type hierarchy, it seems a range could be a Sequence or Set type fairly logically, but I'm unsure which makes more sense, of if it would be better to forgo shoehorning my class into that hierarchy and simply implement the behavior I want.

作为一个序列:


    适用于相当好的规格,它是一个有限订单集。
  • 可以对范围进行计数,切片和迭代。

  • 然而,我可能希望支持无界范围,例如 [0,+∞] ,所以也许上述不是真的。

  • Fits the spec fairly well, it's a "finite ordered set".
  • A range can be counted, sliced, and iterated over.
  • However I potentially want to support unbounded ranges, e.g. [0,+∞), so maybe the above isn't true.

作为一个集合:


  • 作为一个范围被明确排序,稍微不太规范

  • 在概念上更像一个范围,因为集合理论操作如交叉和联合更有意义

  • 正确地表示包含检查是有效的

作为一个单独的结构:


  • 我们失去了遵循上述类型的模式的好处(我们'必须定义一个单独的 range.slice()方法,例如)

  • 但是我们更明确地说,这个结构也不应该与这些类型混淆。事实上,Guava's Range没有实现Collection API,似乎反映了这个争议。

  • We lose the benefits of following the patterns the above types (we'd have to define a separate range.slice() method, for instance)
  • But we're more explicit that this structure should not be confused with these types either. The fact that Guava's Range doesn't implement the Collection API seems to back this argument up.

我很好奇,似乎最多的Pythonic在这里,如果有人自己做了这样的数据结构。

I'm curious what seems most Pythonic here, and if anyone's made any such data structures themselves.

推荐答案

这是我到目前为止的实现。一个范围对象表示任意openClosed范围,并且是可哈希的,可包含的和可迭代的,但既不是序列也不是集合。 DateRange 子类表示日期范围,主要仅需要将增量参数定义为 timedelta(days = 1)而不仅仅是 1

Here's the implementation I've come up with so far. A Range object represents an arbitrary openClosed range, and is hash-able, contain-able, and iter-able, but is neither a sequence nor a set. The DateRange subclass represents ranges of dates, which primarily simply requires defining the increment argument as timedelta(days=1) rather than simply 1.

class Range:  
  '''
  Represents a range, in the spirit of Guava's Range class.
  Endpoints can be absent, and (presently) all ranges are openClosed.
  There's little reason to use this class directly, as the range()
  builtin provides this behavior for integers.
  '''
  def __init__(self, start, end, increment=1):
    if start and end and end < start:
      raise ValueError("End date cannot be before start date, %s:%s" % (start,end))
    self.start = start
    self.end = end
    self.increment = increment

  def __repr__(self):
    return '[%s\u2025%s)' % (
      self.start or '-\u221E',
      self.end   or '+\u221E'
    )

  def __eq__(self, other):
    return self.start == other.start and self.end == other.end

  def __hash__(self):
    return 31*hash(self.start) + hash(self.end)

  def __iter__(self):
    cur = self.start
    while cur < self.end:
      yield cur
      cur = cur + self.increment

  def __contains__(self, elem):
    ret = True
    if self.start:
      ret = ret and self.start <= elem
    if self.end:
      ret = ret and elem < self.end
    return ret

class DateRange(Range):
  '''A range of dates'''
  one_day = timedelta(days=1)

  @staticmethod
  def parse(daterange):
    '''Parses a string into a DateRange, useful for
    parsing command line arguments and similar user input.
    *Not* the inverse of str(range).'''
    start, colon, end = daterange.partition(':')
    if colon:
      start = strToDate(start) if start else None
      end = strToDate(end) if end else None
    else:
      start = strToDate(start)
      end = start + DateRange.one_day
    return DateRange(start, end)

  def __init__(self, start, end):
    Range.__init__(self, start, end, DateRange.one_day)

def strToDate(date_str):
  '''Parses an ISO date string, such as 2014-2-20'''
  return datetime.datetime.strptime(date_str, '%Y-%m-%d').date()

一些使用示例:

>>> DateRange(datetime.date(2014,2,20), None)
[2014-02-20‥+∞)
>>> DateRange(datetime.date(2014,1,1), datetime.date(2014,4,1))
[2014-01-01‥2014-04-01)
>>> DateRange.parse(':2014-2-20')
[-∞‥2014-02-20)
>>> DateRange.parse('2014-2-20:2014-3-22')
[2014-02-20‥2014-03-22)
>>> daterange = DateRange.parse('2014-2-20:2014-3-2')
>>> daterange
[2014-02-20‥2014-03-02)
>>> datetime.date(2014,1,25) in daterange
False
>>> datetime.date(2014,2,20) in daterange
True
>>> list(daterange)
[datetime.date(2014, 2, 20), datetime.date(2014, 2, 21), datetime.date(2014, 2, 22),
 datetime.date(2014, 2, 23), datetime.date(2014, 2, 24), datetime.date(2014, 2, 25),
 datetime.date(2014, 2, 26), datetime.date(2014, 2, 27), datetime.date(2014, 2, 28),
 datetime.date(2014, 3, 1)]

这篇关于将值范围表示为Python序列类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆