在Python中表示一系列值

时间:2014-05-12 22:17:42

标签: python data-structures

我有兴趣在Python中表示一个范围,类似于Guava的Range类型。具体来说,它应该有一个开始和结束,并代表两者之间的所有值(作为第一遍,我很好,仅表示规范的开放闭合范围,即[5,10),但任何开放的正确表示/封闭范围将是一个合理的特征)。

我知道range()内置,但我的意图是支持任意类型(或特别是日期,对于我的用例)。

看看Python的type hierarchy,看起来范围可能是SequenceSet类型的逻辑,但我不确定哪个更有意义,如果它会更好放弃把我的班级放到那个层次结构中,然后简单地实现我想要的行为。

作为Sequence

  • 完全符合规范,这是一个“有限有序集”。
  • 可以对范围进行计数,切片和迭代。
  • 但是,我可能希望支持无限范围,例如[0,+∞),所以上述情况可能不正确。

作为Set

  • 稍微少于规格,因为明确订购范围
  • 概念上更像是一个范围,因为交集和联合等集合理论操作更有意义
  • 正确表示包含检查效率

作为一个单独的结构:

  • 我们失去了遵循上述类型模式的好处(例如,我们必须定义一个单独的range.slice()方法)
  • 但我们更明确的是,这种结构也不应与这些类型相混淆。 Guava的Range没有从Collection API扩展的事实似乎支持了这个论点。

我很好奇这里最恐怖的东西,如果有人自己制作了这样的数据结构。

1 个答案:

答案 0 :(得分:1)

这是我到目前为止所提出的实施方案。 Range对象表示任意openClosed范围,并且是可散列的,包含的,并且非常容易的,但既不是序列也不是集合。 DateRange子类表示日期范围,主要只需要将增量参数定义为timedelta(days=1),而不仅仅是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)]