web-dev-qa-db-ja.com

Pythonで効率的な日付範囲の重複計算?

2つの日付範囲があり、各範囲は開始日と終了日(明らかにdatetime.date()インスタンス)によって決定されます。 2つの範囲は重複しても重複しなくてもかまいません。オーバーラップの日数が必要です。もちろん、両方の範囲内のすべての日付で2つのセットを事前入力してセットの交差を実行できますが、これはおそらく非効率です...すべてのケースをカバーする長いif-Elifセクションを使用する別のソリューションから離れたより良い方法はありますか?

70
Andreas Jung
  • 2つの開始日の最新の日付と2つの終了日の最も早い日付を決定します。
  • それらを減算してtimedeltaを計算します。
  • デルタが正の場合、それはオーバーラップの日数です。

以下に計算例を示します。

>>> from datetime import datetime
>>> from collections import namedtuple
>>> Range = namedtuple('Range', ['start', 'end'])

>>> r1 = Range(start=datetime(2012, 1, 15), end=datetime(2012, 5, 10))
>>> r2 = Range(start=datetime(2012, 3, 20), end=datetime(2012, 9, 15))
>>> latest_start = max(r1.start, r2.start)
>>> earliest_end = min(r1.end, r2.end)
>>> delta = (earliest_end - latest_start).days + 1
>>> overlap = max(0, delta)
>>> overlap
52
150

関数呼び出しは算術演算よりも高価です。

これを行う最速の方法には、2つの減算と1 min()が含まれます。

min(r1.end - r2.start, r2.end - r1.start).days + 1

1減算、1 min()およびmax()を必要とする次善策と比較して:

(min(r1.end, r2.end) - max(r1.start, r2.start)).days + 1

もちろん、両方の式を使用して、ポジティブなオーバーラップを確認する必要があります。

8
John Machin

以下に示すように、TimeRangeクラスを実装しました。

Get_overlapped_rangeは、最初に単純な条件ですべての非重複オプションを無効にし、次にすべての可能なオプションを考慮して重複範囲を計算します。

日数を取得するには、get_overlapped_rangeから返されたTimeRange値を取得し、期間を60 * 60 * 24で割る必要があります。

class TimeRange(object):
    def __init__(self, start, end):
        self.start = start
        self.end = end
        self.duration = self.end - self.start

    def is_overlapped(self, time_range):
        if max(self.start, time_range.start) < min(self.end, time_range.end):
            return True
        else:
            return False

    def get_overlapped_range(self, time_range):
        if not self.is_overlapped(time_range):
            return

        if time_range.start >= self.start:
            if self.end >= time_range.end:
                return TimeRange(time_range.start, time_range.end)
            else:
                return TimeRange(time_range.start, self.end)
        Elif time_range.start < self.start:
            if time_range.end >= self.end:
                return TimeRange(self.start, self.end)
            else:
                return TimeRange(self.start, time_range.end)

    def __repr__(self):
        return '{0} ------> {1}'.format(*[time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(d))
                                          for d in [self.start, self.end]])
5
Elad Sofer

擬似コード:

 1 + max( -1, min( a.dateEnd, b.dateEnd) - max( a.dateStart, b.dateStart) )
3
ypercubeᵀᴹ
def get_overlap(r1,r2):
    latest_start=max(r1[0],r2[0])
    earliest_end=min(r1[1],r2[1])
    delta=(earliest_end-latest_start).days
    if delta>0:
        return delta+1
    else:
        return 0
0
andros1337