在Python字典中选择一段日期范围

4

我有以下字典:

history = {
"2008-11-17": 41, 
"2010-05-28": 82, 
"2008-11-14": 47, 
"2008-11-13": 60, 
"2008-11-12": 56, 
"2008-11-11": 55, 
"2008-11-10": 98, 
"2008-11-19": 94, 
"2008-11-18": 94, 
"2004-05-27": 82, 
"2004-05-26": 45, 
"2004-05-25": 70,
# there's more ...
}

如何定义一个生成器函数 get_records(dict_history, str_from_date, str_to_date),以产生 date: record 条目?

我知道如何将 datetime 对象转换为任何我想要的字符串格式。然而,在这个障碍中,我的主要痛点是:

  1. dict 不是有序的。
  2. dict 键是字符串。
  3. 日期不连续。

到目前为止,这是我能想到的:

from datetime import datetime, timedelta

def get_records(history, start_date, end_date):
  fmt = "%Y-%m-%d"
  dt = timedelta(days=1)

  present_date = datetime.strptime(start_date, fmt)
  end_date = datetime.strptime(end_date, fmt)

  while present_date <= end_date:
    present_string = present_date.strftime(fmt)
    try:
      yield (present_string, history[present_string])
    except KeyError:
      pass
    present_date += dt

有更有效的方法吗?

更新(2011年8月2日)
我在ActiveState找到了Raymond Hettinger创建的SortedCollection类。


既然您的范围以相同格式的字符串给出,并且日期已经按正确顺序排列(Y M D),为什么不只需遍历字典中的项并按“start<=key<=end”进行过滤呢? - pyroscope
@sverre:如果我一开始就把history作为一个已排序的元组list(这是可以做到的,因为我可以控制如何格式化history),那么通过二分查找list可能是一个不错的选择。但当我问自己“我该如何查询我想要的日期?”时,我就被卡住了。如果你能为此提供答案,我将非常欢迎。 - Kit
1
Python 2.7和3.1支持有序字典。你研究过它们吗?旧版Python实现:http://pypi.python.org/pypi/ordereddict - Warren P
@Warren,我正在使用2.5版(Google App Engine)。 - Kit
1
请查看链接。这里有一款由Raymond Hettinger编写的插件。 - Warren P
显示剩余4条评论
5个回答

6
我会遍历字典并返回匹配的项:

我只需遍历字典并返回匹配的项:

def get_records(history, start_date, end_date):
    for date, entry in history.iteritems():
        if start_date <= date <= end_date:
             yield date, entry

请注意,您特定的日期格式允许直接使用 <> 进行字符串比较,无需先转换为 datetime 实例。
另请注意,给定函数将以任意顺序返回匹配的项目。

是的,但是你的解决方案(以及我在问题中提供的示例)必须遍历整个字典。我预计会有超过3000个条目,遍历整个字典可能会显著减慢速度。 - Kit
@Kit:你的解决方案必须迭代区间的天数,而我的解决方案必须迭代字典的条目。我提出我的解决方案是为了可读性和简单性,而不是为了性能。但是3000似乎不是一个非常大的数字,所以可能这已经足够快了。如果不是,请使用更适合的数据结构。 - Sven Marnach
哦,对了,我没有注意到我的日常迭代。我会看看你的解决方案能得到什么结果,或者使用更好的数据结构(我还没有找到)。 - Kit

0

这样怎么样:

def get_records(history, start_date, end_date, format = "%Y-%m-%d"):
    present_date = datetime.strptime(start_date, format)
    end_date = datetime.strptime(end_date, format)
    return [(key, value) for key, value in history.items() if present_date <= datetime.strptime(history[key], format) <= end_date]

0
history = { "2008-11-17": 41,
            "2010-05-28": 82,
            "2008-11-14": 47,
            "2008-11-13": 60,
            "2008-11-12": 56,
            "2008-11-11": 55,
            "2008-11-10": 98,
            "2008-11-19": 94,
            "2008-11-18": 94,
            "2004-05-27": 82,
            "2004-05-26": 45,
            "2004-05-25": 70  }



def get_records(dict_history, str_from_date, str_to_date):

    for k,v in sorted(dict_history.items()):
        if k>str_to_date:
            break
        if k>=str_from_date:
            yield (k,v)

print history.items()
print
print list( get_records(history, '2005-05-21', '2008-12-25'))  

日期是字符串 'yyyy-mm-jj'

按字典顺序排序这些字符串会产生与按它们所代表的日期排序相同的结果。

sorted(dict_history.items()) 是一个元组列表。Python 根据元组的第一个元素对此列表进行排序。
由于字典中的每个键都是唯一的,因此在此排序中不存在歧义。

编辑 1

回答您的性能问题:

history = { "2008-11-17": 41,
            "2010-05-28": 82,
            "2008-11-14": 47,
            "2008-11-13": 60,
            "2008-11-12": 56,
            "2008-11-11": 55,
            "2008-11-11": 02,
            "2008-11-10": 98,
            "2008-11-19": 94,
            "2008-11-18": 94,
            "2004-05-27": 82,
            "2004-05-26": 45,
            "2004-05-25": 70  }
import bisect

def get_records(dict_history, str_from_date, str_to_date):
    sorted_keys  = sorted(dict_history.iterkeys())
    start = bisect.bisect_left(sorted_keys,str_from_date)
    end   = bisect.bisect_right(sorted_keys,str_to_date)
    for date in sorted(dict_history.iteritems())[start:end]:
        yield date

print history.items()
print
print list( get_records(history, '2005-05-21', '2008-12-25')) 

0
def get_records(history, str_from_date, str_to_date)
    return sorted((k,v) for k,v in history.iteritems() if str_from_date<=k<=str_to_date)

0

这种方法只需要对列表进行一次排序,就可以通过日期行。

from datetime import datetime, timedelta

def get_records(history, start_date, end_date):
  fmt = "%Y-%m-%d"

  start_date = datetime.strptime(start_date, fmt)
  end_date = datetime.strptime(end_date, fmt)

  dt = history.iteritems()
  dt = sorted(dt, key= lambda date: datetime.strptime(date[0], fmt))

  for date in dt:
      if datetime.strptime(date[0],fmt) > end_date:
          break
      elif datetime.strptime(date[0],fmt) >= start_date:
          yield(date[0], history[date[0]])
      else:
          pass

哎呀,赶在有人给我投反对票之前修复了明显的初学者错误! - Chris Huang-Leaver

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接