【问题标题】:Efficiently finding the previous key in an OrderedDictionary在 OrderedDictionary 中有效地查找前一个键
【发布时间】:2019-03-03 07:10:03
【问题描述】:

我有一个包含费率值的 OrderedDictionary。每个条目都有一个键的日期(每个日期恰好是年度季度的开始),值是一个数字。日期按从旧到新的顺序插入。

{
    date(2017, 1, 1): 95,
    date(2018, 1, 1): 100,
    date(2018, 6, 1): 110,
    date(2018, 9, 1): 112,
}

我的费率字典比这大得多,但这是一般的想法。给定一个任意日期,我想在字典中找到 precedes 它的值。例如,查找date(2018, 8, 1) 的日期应该返回值110,因为条目date(2018, 6, 1) 是我查找日期之前最近的键。同样,date(2017, 12, 1) 的日期应该返回 95,因为最近的前面键恰好是 date(2017, 1, 1)

我可以通过遍历字典中的项目轻松做到这一点:

def find_nearest(lookup):
    nearest = None
    for d, value in rates.items():
        if(d > lookup):
            break
        nearest = value
    return nearest

然而,这对我来说效率低下,因为在最坏的情况下我必须扫描整个字典(我之前提到的字典可能很大)。我将进行数以万计的此类查找,因此我希望它具有高性能。

解决性能问题的另一个选择是创建我所见内容的缓存,这也是可行的,但我想知道内存限制(我不完全确定缓存会增长多大)。

我可以在这里使用任何聪明的方法或 Python 核心模块吗?

【问题讨论】:

  • 我认为你应该在这里使用二叉树(或排序列表),这样你就可以在 O(log n) 中查询。或者有一些数据结构用链表对字典进行编码,这样人们就可以获得下一个和上一个元素。

标签: python python-3.x


【解决方案1】:

由于OrderedDict 是通过链表实现的,因此您不能在小于 O(n) 的时间内直接按位置检索值,尽管您可以利用对键进行排序以减少到 O(记录n)。另见:Accessing dictionary items by position in Python 3.6+ efficiently

为了提高效率,我建议您使用第三方库,例如 Pandas,它使用保存在连续内存块中的 NumPy 数组。时间复杂度为 O(n),但您应该会看到更大输入字典的性能有所提高。

import pandas as pd
from datetime import date

d = {date(2017, 1, 1): 95, date(2018, 1, 1): 100,
     date(2018, 6, 1): 110, date(2018, 9, 1): 112}

df = pd.DataFrame.from_dict(d, orient='index')
df.index = pd.to_datetime(df.index)

my_date = pd.to_datetime(date(2018, 8, 1))
res = df[0].iat[df.index.get_loc(my_date, method='ffill')]  # 110

另一种更详细的方法:

diffs = (my_date - df.index) > pd.Timedelta(0)
res = df[0].iat[-(diffs[::-1].argmax() + 1)]                # 110

【讨论】:

    【解决方案2】:

    由于您按顺序将日期插入到 dict 中,并且您可能正在使用 Python 3.7(这使得 dict 顺序很重要),您可以使用递归函数进行分治来找到 O 中键列表的所需索引(log n) 时间复杂度:

    def find_nearest(l, lookup):
        if len(l) == 1:
            return l[0]
        mid = len(l) // 2
        if l[mid] > lookup:
            return find_nearest(l[:mid], lookup)
        return find_nearest(l[mid:], lookup)
    

    这样:

    from datetime import date
    d = {
        date(2017, 1, 1): 95,
        date(2018, 1, 1): 100,
        date(2018, 6, 1): 110,
        date(2018, 9, 1): 112,
    }
    d[find_nearest(list(d), date(2018, 8, 1))]
    

    返回:110

    【讨论】:

      【解决方案3】:

      编辑 我刚刚意识到你想要一个核心模块——我的答案是使用 pandas!

      如果您有唯一的日期值,您可以使用 pandas 创建一个使用日期作为索引的数据框:

      df = pd.DataFrame.from_dict(rates, orient='index', columns=['value'])
      # Convert index to pandas datetime
      df.index = pd.to_datetime(df.index)
      

      这会返回:

                    value
      2017-01-01     95
      2018-01-01    100
      2018-06-01    110
      2018-09-01    112
      

      然后:

      def lookup(date, df):
          # Convert input to datetime
          date = pd.to_datetime(date)
          # Get closest date in index
          closest_date = min(df.index, key=lambda x: abs(x - date))
          # Find corresponding index of closest date
          index = np.where(df.index == closest_date)[0][0]
          # If the date found if greater than the input, then get the date at the index before
          if closest_date > date:
              index -= 1
      
          return df.iloc[index].value
      
      >> lookup('2018-06-02', df)
      Out: 110
      
      >> lookup('2018-05-01', df)
      Out: 100
      

      【讨论】:

      • 现在如果您传递给lookup() 的日期不在数据框中怎么办? (这是操作要求的)。
      【解决方案4】:

      sortedcontainers 可能是你想要的。

      它将保持键的排序顺序而不是插入顺序,这与collections.OrderedDict不同。

      安装

      $ pip install sortedcontainers
      

      实现你想要的

      from sortedcontainers import SortedDict
      def find_nearest(sorted_dict, lookup):
          key = sorted_dict.iloc[sorted_dict.bisect_left(lookup) - 1]
          return sorted_dict[key]
      
      sd = SortedDict({0: '0', 4: '4', 8: '8', 12: '12'})
      print(find_nearest(sd, 4))  # 0
      print(find_nearest(sd, 3))  # 0
      print(find_nearest(sd, 12))  # 8 
      

      这个方法的时间复杂度是O(log n)

      【讨论】:

        【解决方案5】:

        你可以试试 .get() 方法,它只在存在时返回一个值,否则返回 None

        import datetime
        from datetime import date
        
        def findNearest(somedate, dictionary):
            while dictionary.get(somedate) is None:
                somedate=somedate-datetime.timedelta(1)
        
            return dictionary.get(somedate)
        
        
        result=findNearest(date(2017, 1, 3), yourDictionary)
        

        当您打印结果时,它将打印“95”,即 date(2017, 1, 1) 的值

        【讨论】:

        • @brunodesthuilliers 这取决于数据,首先他们假设一整天,然后如果日期相当接近可能没问题,如果日期相隔 10000 年,那就不好了
        猜你喜欢
        • 1970-01-01
        • 2016-06-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-12-23
        • 2011-01-15
        • 1970-01-01
        相关资源
        最近更新 更多