对于列表中的每个元素,请查找其他列表中的最接近日期

时间:2018-11-02 14:57:45

标签: python list

我有2个列表:

l1 = [ '09/12/2017', '10/24/2017' ]
l2 = [ '09/15/2017', '10/26/2017', '12/22/2017' ]

对于l1中的每个股票行情指示器,我想查找l2之后最接近的元素,因此输出应为

l3 = [ '09/15/2017', '10/26/2017' ]

正确的方法似乎是明确地以相反的顺序对两个列表进行并行迭代,但是我希望有一个更“ pythonic”的解决方案。

编辑:我确实想要一个最佳复杂性解决方案,该解决方案(假设列表已排序),我认为是O(max(len(l1),len(l2)))。

4 个答案:

答案 0 :(得分:6)

通过传递min 表达式,可以将列表理解lambda方法结合使用。

from datetime import datetime
l1 = [ '09/12/2017', '10/24/2017' ]
l2 = [ '09/15/2017', '10/26/2017', '12/22/2017' ]

l1 = [min(l2, key=lambda d: abs(datetime.strptime(d, "%m/%d/%Y") - datetime.strptime(item, "%m/%d/%Y"))) for item in l1]

输出

['09/15/2017', '10/26/2017']

如果您想要更有效的解决方案,则可以编写自己的insert排序算法。

def insertSortIndexItem(lst, item_to_insert):
  index = 0
  while index < len(lst) and item_to_insert > lst[index]:
    index = index + 1
  return lst[index]

l2 = sorted(l2, key=lambda d: datetime.strptime(d, "%m/%d/%Y"))
l1 = [insertSortIndexItem(l2, item) for item in l1]

答案 1 :(得分:3)

如果列表很长,则值得进行l2预处理,以便能够使用bisect查找最接近的日期。然后,在l1中找到最接近日期的日期将是O {log(len(l2)),而不是min的O(len(l2))。

from datetime import datetime
from bisect import bisect

l1 = [ '09/12/2017', '10/24/2017' ]
l2 = [ '09/15/2017', '10/26/2017', '12/22/2017' ]

dates = sorted(map(lambda d: datetime.strptime(d, '%m/%d/%Y'), l2))

middle_dates = [dates[i] + (dates[i+1]-dates[i])/2 for i in range(len(dates)-1)]

out = [l2[bisect(middle_dates, datetime.strptime(d,'%m/%d/%Y'))] for d in l1]

print(out)
# ['09/15/2017', '10/26/2017']

要解决您的最后一条评论,这是另一个使用迭代器和生成器的解决方案,它遍历l1,并且仅覆盖l2开头的必要部分:

from datetime import datetime
from itertools import tee, islice, zip_longest

def closest_dates(l1, l2):
    """
    For each date in l1, finds the closest date in l2,
    assuming the lists are already sorted.
    """
    dates1 = (datetime.strptime(d, '%m/%d/%Y') for d in l1)
    dates2 = (datetime.strptime(d, '%m/%d/%Y') for d in l2)
    dinf, dsup = tee(dates2)
    enum_middles = enumerate(d1 + (d2-d1)/2 
                             for d1, d2 in zip_longest(dinf, islice(dsup, 1, None), 
                                                       fillvalue=datetime.max))
    out = []
    index, middle = next(enum_middles)

    for d in dates1:
        while d > middle:
            index, middle = next(enum_middles)
        out.append(l2[index])

    return out

一些测试:

l1 = [ '09/12/2017', '10/24/2017', '12/11/2017', '01/04/2018' ]
l2 = [ '09/15/2017', '10/26/2017', '12/22/2017' ]
print(closest_dates(l1, l2))
# ['09/15/2017', '10/26/2017', '12/22/2017', '12/22/2017']

l2 = ['11/11/2018']  # only one date, it's always the closest
print(closest_dates(l1, l2))
# ['11/11/2018', '11/11/2018', '11/11/2018', '11/11/2018']

答案 2 :(得分:1)

假设您的示例中的日期按时间顺序排列,则可以利用列表已排序的事实。例如,如果您乐于使用第三方库,则可以通过np.searchsorted使用NumPy,它是标准库中bisect的更快版本:

import numpy as np
from datetime import datetime

l1 = [ '09/12/2017', '10/24/2017' ]
l2 = [ '09/15/2017', '10/26/2017', '12/22/2017' ]

l1_dt = [datetime.strptime(i, '%d/%M/%Y') for i in l1]
l2_dt = [datetime.strptime(i, '%d/%M/%Y') for i in l2]

res = list(map(l2.__getitem__, np.searchsorted(l2_dt, l1_dt)))

# ['09/15/2017', '10/26/2017']

答案 3 :(得分:0)

您可以使用可计算两个日期之间时间差的键功能进行排序。

from datetime import datetime
print([min(l2, key=lambda s: abs((datetime.strptime(s, '%m/%d/%Y') - datetime.strptime(d, '%m/%d/%Y')))) for d in l1])

这将输出:

['09/15/2017', '10/26/2017']

请注意,date format string应该分别为%m/%d/%Y,分别表示月份,日期和年份。