Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Recursive list functions

I am trying to create a recursive Python function that accepts a list of periods and consolidates them into a clean timeline. It should scan through a list and apply these rules:

  • If value of None is found within period: replace None with datetime.date.today()

  • If a period starts within and ends within another period: delete it.

  • If a period starts before but ends within another period: extend start date.

  • If a period ends after but begins within another period: extend end date.

  • If a period starts after and ends after another period: keep it, it's a separate period.

  • If a period starts before and ends before another period: keep it, it's a separate period.

It is perhaps much easier to give an example of a input and desired output (assume values are formatted with datetime):

[I] = [(01/2011, 02/2015), (04/2012, 08/2014), (09/2014, 03/2015), (05/2015, 06/2016)]
[O] = [(01/2011, 03/2015), (05/2015, 06/2016)]  
# Notice how the output has produced a set of minimum length whilst covering all periods.

[I] = [(07/2011, 02/2015), (04/2012, 08/2014), (09/2014, 04/2015), (06/2015, None)]
[O] = [(07/2011, 04/2015), (06/2015, date.today())]
# Also, notice how the output has amended None, so it can compare dates.

Thanks to @khredos, I have written the following, but it still does not output the minimum string required:

from datetime import datetime

# Here is an example list of time periods
periods = [('01/2011', '02/2015'), ('04/2012', '08/2014'), ('09/2014', '03/2015'), ('05/2015', '06/2016')]

# this lambda function converts a string of the format you have specified to a 
# datetime object. If the string is None or empty, it uses today's date
cvt = lambda ds: datetime.strptime(ds, '%m/%Y') if ds else datetime.today()

# Now convert your original list to an iterator that contains datetime objects
periods = list(map(lambda s_e : (cvt(s_e[0]), cvt(s_e[1])), periods))

# Next get the start dates into one list and the end dates into another
starts, ends = zip(*periods)

# Finally get the timeline by sorting the two lists
timeline = sorted(starts + ends)

# Output: [datetime.datetime(2011, 1, 1, 0, 0), datetime.datetime(2012, 4, 1, 0, 0), datetime.datetime(2014, 8, 1, 0, 0), datetime.datetime(2014, 9, 1, 0, 0), datetime.datetime(2015, 2, 1, 0, 0), datetime.datetime(2015, 3, 1, 0, 0), datetime.datetime(2015, 5, 1, 0, 0), datetime.datetime(2016, 6, 1, 0, 0)]
like image 775
alpacinohead Avatar asked Dec 10 '25 06:12

alpacinohead


1 Answers

from datetime import datetime

# Here is an example list of time periods
periods = [('01/2011', '02/2015'), ('04/2012', '08/2014'), ('09/2014', '03/2015'), ('05/2015', '06/2016')]

# this lambda function converts a string of the format you have specified to a 
# datetime object. If the string is None or empty, it uses today's date
cvt = lambda ds: datetime.strptime(ds, '%m/%Y') if ds else datetime.today()

The available formats are here

# Now convert your original list to an iterator that contains datetime objects
periods = list(map(lambda s_e : (cvt(s_e[0]), cvt(s_e[1])), periods))

# Next get the start dates into one list and the end dates into another
starts, ends = zip(*periods)

# Finally get the timeline by sorting the two lists
timeline = sorted(starts + ends)

The output should be similar to

[datetime.datetime(2011, 1, 1, 0, 0), datetime.datetime(2012, 4, 1, 0, 0), datetime.datetime(2014, 8, 1, 0, 0), datetime.datetime(2014, 9, 1, 0, 0), datetime.datetime(2015, 2, 1, 0, 0), datetime.datetime(2015, 3, 1, 0, 0), datetime.datetime(2015, 5, 1, 0, 0), datetime.datetime(2016, 6, 1, 0, 0)]

Try it with any list of dates you have and you should observe the same behaviour.

HTH

like image 121
smac89 Avatar answered Dec 12 '25 20:12

smac89



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!