表示Python中的值范围 [英] Representing a Range of values in Python

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

问题描述

我有兴趣代表一个范围,类似于Guava的 Range 类型,在Python中。具体来说,它应该有一个起点和终点,并代表两者之间的所有值(作为第一步,我很好,只代表标准的开闭范围,即 [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() 内置,但是我的目的是支持任意类型(对于我的用例而言,具体来说是日期)。

I'm aware of the range() builtin, but my intent is to support arbitrary types (or specifically dates, for my use case).

看看Python的类型层次结构,似乎范围可能是序列 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.

作为 Set


  • 规格范围略小被显式排序

  • 从概念上讲,它更像是一个范围,因为诸如交集和联合之类的集合理论操作更有意义

  • 正确地表示包含支票是有效的

作为单独的结构:


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

  • 但是我们更加明确的是,该结构也不应与这些类型混淆。番石榴的 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 extend from the Collection API seems to back this argument up.

我很好奇这里似乎是最Python化的,如果有人自己制作了这样的数据结构。

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

推荐答案

这是到目前为止我提出的实现。 Range 对象表示任意的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天全站免登陆