dabadaba
dabadaba

Reputation: 9522

Condensed and efficient way of selecting time slots within a time range

I have a bunch of time slots with a start and an end of type datetime. Given a particular datetime, I need to get those that are before, and after:

def get_before(timeslots, moment):
    return [t for t in timeslots if t.end <= moment]

def get_after(timeslots, moment):
    return [t for t in timeslots if t.start >= moment]

But there are two optional arguments, minimum and maximum, indicating that the time slots must be within the maximum amount of minutes and outside the minimum.

We have the following slots:

2018-06-27 09:00:00 - 2018-06-27 10:00:00
2018-06-27 10:00:00 - 2018-06-27 11:00:00
2018-06-27 11:00:00 - 2018-06-27 12:00:00
2018-06-27 12:00:00 - 2018-06-27 13:00:00
2018-06-27 13:00:00 - 2018-06-27 14:00:00
2018-06-27 14:00:00 - 2018-06-27 15:00:00
2018-06-27 15:00:00 - 2018-06-27 16:00:00
2018-06-27 16:00:00 - 2018-06-27 17:00:00
2018-06-27 17:00:00 - 2018-06-27 18:00:00
2018-06-27 18:00:00 - 2018-06-27 19:00:00
2018-06-27 19:00:00 - 2018-06-27 20:00:00
2018-06-27 20:00:00 - 2018-06-27 21:00:00
2018-06-27 21:00:00 - 2018-06-27 22:00:00

If we want the time slots after 2018-06-27 15:00:00, with a minimum of 1 hour and a maximum of 4 hours, we get:

2018-06-27 16:00:00 - 2018-06-27 17:00:00
2018-06-27 17:00:00 - 2018-06-27 18:00:00
2018-06-27 18:00:00 - 2018-06-27 19:00:00
2018-06-27 19:00:00 - 2018-06-27 20:00:00

This is my implementation:

def get_before(timeslots, moment, minimum=None, maximum=None):
    tslots = [t for t in timeslots if t.end <= moment]
    if maximum is not None:
        maxdelta = datetime.timedelta(minutes=maximum)
        tslots = [t for t in tslots if t.end + maxdelta >= moment]
    if minimum is not None:
        mindelta = datetime.timedelta(minutes=minimum)
        tslots = [t for t in tslots if t.end <= moment - mindelta]
    return tslots


def get_after(timeslots, moment, minimum=None, maximum=None):
    tslots = [t for t in timeslots if t.start >= moment]
    if maximum is not None:
        maxdelta = datetime.timedelta(minutes=maximum)
        tslots = [t for t in tslots if t.start - maxdelta <= moment]
    if minimum is not None:
        mindelta = datetime.timedelta(minutes=minimum)
        tslots = [t for t in tslots if t.start >= moment + mindelta]
    return tslots

The problem is that for each filtering function, I am iterating the timeslots list three times: one to get those before or after the moment, secondly to get those within the maximum time range, and thirdly to filter those outside the minimum time range.

These function are going to be called very frequently, so I wonder if there is a way I could merge the filtering so that the list is iterated only once.

Upvotes: 0

Views: 332

Answers (3)

bobrobbob
bobrobbob

Reputation: 1281

just compute everything once and set useful default values:

def get_before2(timeslots, moment, minimum=0, maximum=1440):
    maxdelta = moment - datetime.timedelta(minutes=maximum)
    mindelta = moment - datetime.timedelta(minutes=minimum)
    tslots = [t for t in timeslots if t.end >= maxdelta and t.end <= mindelta]  

Upvotes: 0

shrewmouse
shrewmouse

Reputation: 6030

You can create a lambda function based on the presences of maximum and minimum. Then apply that function in a single list comprehension.

maximum = 10
minimum = 5

if maximum:
    f = lambda x : x <= maximum

if minimum:
    f = lambda x : x >= minimum

if maximum and minimum:
    f = lambda x : x <= maximum and x >= minimum


print [ x for x in range(50) if f(x) ]

output:

[sri@localhost ~]$ python test.py
[5, 6, 7, 8, 9, 10]

Also, if your times are sorted you can stop searching once you find the maximum.

Upvotes: 0

Bjerrum
Bjerrum

Reputation: 108

This can be solved effeciently with an interval tree. See https://en.wikipedia.org/wiki/Interval_tree

There seem to be many python implementations according to a quick google search.

Upvotes: 1

Related Questions