【问题标题】:how to sort date strings in a dictionary如何对字典中的日期字符串进行排序
【发布时间】:2013-06-23 14:57:46
【问题描述】:

如何获取某个日期后的第一个密钥?

date_table 变大时最好的解决方案是什么?

def get_first():
    date_table = {
        'this is example 1': '01:20 2013-08-07',
        'this is example 2': '11:45 2012-03-23',
        'this is example 3': '19:00 2013-12-01', 
    }
    certain_date = '12:14 2013-06-23'
    # TODO: sort, filter

print get_first()
>> 'this is example 1'

【问题讨论】:

    标签: python sorting dictionary


    【解决方案1】:

    您必须排序然后过滤,以及解析结构中的所有日期:

    from datetime import datetime
    
    certain_date = datetime.strptime(certain_date, '%H:%M %Y-%m-%d')
    match = next((k for v, k in sorted((datetime.strptime(v, '%H:%M %Y-%m-%d'), k) for k, v in date_table.iteritems()) if v >= certain_date), None)
    

    演示:

    >>> certain_date = datetime.strptime(certain_date, '%H:%M %Y-%m-%d')
    >>> next((k for v, k in sorted((datetime.strptime(v, '%H:%M %Y-%m-%d'), k) for k, v in date_table.iteritems()) if v >= certain_date), None)
    'this is example 1'
    

    另一种方法是过滤所有之后且最接近您的搜索日期的日期:

    from datetime import datetime, timedelta
    
    parse = lambda d: datetime.strptime(d, '%H:%M %Y-%m-%d')
    certain_date = parse(certain_date)
    match = min(date_table, key=lambda k: parse(date_table[k]) - certain_date if parse(date_table[k]) > certain_date else timedelta.max)
    

    演示:

    >>> min(date_table, key=lambda k: parse(date_table[k]) - certain_date if parse(date_table[k]) > certain_date else timedelta.max)
    'this is example 1'
    

    您确实想重新考虑您的结构,并使用诸如堆队列或 btree 之类的东西来使您的数据结构更易于进行这种访问。

    即使是带有已解析 (datetime, key) 元组的排序列表也会执行很多更好,因为 bisect module 可以让您在 O(log n) 时间内找到您的“下一个”值,而不是 O (n log n) 用于排序或复杂 min() 过滤器的 O(n)。

    您可以通过以下方式快速将您的结构变成这样的列表:

    from functools import total_ordering
    
    @total_ordering
    class Entry(object):
        def __init__(dt, key):
            self.dt = dt
            self.key = key
    
        def __eq__(self, other):
            if not isinstance(other, type(self)): return NotImplemented
            return self.dt == other.dt and self.key == other.key
    
        def __lt__(self, other):
            if not isinstance(other, type(self)): return NotImplemented
            if self.dt < other.dt:
                return True
            return self.dt == other.dt and  self.key < other.key
    
    date_list = [Entry(datetime.strptime(v, '%H:%M %Y-%m-%d'), k) for v, k in date_table.iteritems()]
    date_list.sort()
    

    然后找到你的下一场比赛:

    import bisect
    match = date_list[bisect.bisect(date_list, Entry(current_date, None))]
    

    您使用bisect.insort() 保持列表排序。

    【讨论】:

    • 作为一个 .net 人,我环顾四周,我很惊讶 python 没有内置的 bst ......除非我错过了什么,只是个人观察 btw.. 与你的答案:)
    • 我发现二叉搜索树总是需要额外的调整,而这在库中很难捕捉到。他们很容易自己写。
    • 二叉搜索树本身很简单,但并不好,需要平衡,这可能会有点麻烦。
    【解决方案2】:

    date_table 变大时最好的解决方案是什么?

    字典不太适合范围检索(即当您想根据键检索一系列值时)。这是因为字典使用散列来存储键,因此不能保证排序顺序,但是为了这种权衡,它们确实提供了对任何特定键的持续时间查找。

    对于范围检索,您最好的选择是使用某种形式的平衡二叉搜索树,如果您用谷歌搜索,我相信 Python 有很多实现。这些允许您在对数时间执行范围检索,这显然比常数慢,但绝对比线性快。

    话虽如此,如果您绝对知道您的字典不会超过某个小尺寸,那么对键使用线性搜索是完全可以接受的,因为性能差异可以忽略不计。

    【讨论】:

      【解决方案3】:

      您可以在此处使用datetime 模块和min

      >>> from datetime import datetime, timedelta
      >>> certain_date = '12:14 2013-06-23'
      >>> c_d = datetime.strptime(certain_date, "%H:%M %Y-%m-%d")
      >>> def func(x):
              d =  datetime.strptime(x[1], "%H:%M %Y-%m-%d")
              delta =  d - c_d if d > c_d else timedelta.max
              return delta
      ... 
      >>> min(date_table.items(), key = func)
      ('this is example 1', '01:20 2013-08-07')
      >>> min(date_table.items(), key = func)[0]
      'this is example 1'
      

      datetime.strptime 将日期转换为日期时间对象,因此c_d 现在看起来像这样:

      >>> c_d
      datetime.datetime(2013, 6, 23, 12, 14)
      

      现在在func:

      delta =  d - c_d if d > c_d else timedelta.max
      

      这会检查当前项目的日期是否比c_d 新,如果是,则返回它们的差异,否则返回timedelta.max

      timedelta.max 是:

      >>> timedelta.max
      datetime.timedelta(999999999, 86399, 999999)
      

      【讨论】:

        【解决方案4】:

        您甚至可以在不将字符串转换为 datetime 对象的情况下逃脱,这里有一个使用 bisect 的选项:

        from operator import itemgetter
        from bisect import bisect
        
        name, tds = zip(*sorted(( (k, v.split()[::-1]) for k, v in date_table.iteritems()), key=itemgetter(1)))
        certain_date = '12:14 2013-06-23'.split()[::-1]
        print name[bisect(tds, certain_date)]
        # this is example 1
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2019-10-29
          • 2021-08-20
          • 2015-08-21
          • 1970-01-01
          • 2020-05-26
          • 1970-01-01
          • 1970-01-01
          • 2021-12-18
          相关资源
          最近更新 更多