Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Find maximum value of time in list containing tuples of time in format ('hour', 'min', 'AM/PM')

I have a list of of tuples that represent different times

timeList = [('4', '12', 'PM'), ('8', '23', 'PM'), ('4', '03', 'AM'), ('1', '34', 'AM'), 
('12', '48', 'PM'), ('4', '13', 'AM'), ('11', '09', 'AM'), ('3', '12', 'PM'), 
('4', '10', 'PM')]

I want to return the max from the list, after some searching I realized I could use the key in max to search by the AM or PM first.
print(max(timeList, key = operator.itemgetter(2)))

When I run this however, I'm getting the wrong max ('4', '12', 'PM') I thought about it, and not only does it not make sense, given that 8:23 should be max, but I also realized that 12:48 would probably return max since it's a PM and also technically greater than 8 in my search.

That being said, how might I get this max to find the latest possible time, given formatting of the list can not be changed.

like image 948
Podo Avatar asked Feb 16 '18 00:02

Podo


3 Answers

Just define an appropriate key-function. You want int(hour), int(minute) and 'PM' already sorts lexicographically higher than "AM", but it should be considered first, so. Also, you need to take the hours modulus 12, so that 12 sorts less than other numbers, within a pm/am:

In [39]: timeList = [('4', '12', 'PM'), ('8', '23', 'PM'), ('4', '03', 'AM'), ('1', '34', 'AM'),
    ...: ('12', '48', 'PM'), ('4', '13', 'AM'), ('11', '09', 'AM'), ('3', '12', 'PM'),
    ...: ('4', '10', 'PM')]

In [40]: def key(t):
...:     h, m, z = t
...:     return z, int(h)%12, int(m)
...:

In [41]: max(timeList,key=key)
Out[41]: ('8', '23', 'PM')

But what would make the most sense is to actually use datetime.time objects, instead of pretending a tuple of strings is a good way to store time.

So something like:

In [49]: def to_time(t):
    ...:     h, m, z = t
    ...:     h, m = int(h)%12, int(m)
    ...:     if z  == "PM":
    ...:         h += 12
    ...:     return datetime.time(h, m)
    ...:

In [50]: real_time_list = list(map(to_time, timeList))

In [51]: real_time_list
Out[51]:
[datetime.time(16, 12),
 datetime.time(20, 23),
 datetime.time(4, 3),
 datetime.time(1, 34),
 datetime.time(12, 48),
 datetime.time(4, 13),
 datetime.time(11, 9),
 datetime.time(15, 12),
 datetime.time(16, 10)]

In [52]: list(map(str, real_time_list))
Out[52]:
['16:12:00',
 '20:23:00',
 '04:03:00',
 '01:34:00',
 '12:48:00',
 '04:13:00',
 '11:09:00',
 '15:12:00',
 '16:10:00']

Note, now max "just works":

In [54]: t = max(real_time_list)

In [55]: print(t)
20:23:00

And if you need a pretty string to print, just do the formatting at that point:

In [56]: print(t.strftime("%I:%M %p"))
08:23 PM
like image 172
juanpa.arrivillaga Avatar answered Sep 27 '22 21:09

juanpa.arrivillaga


Why not add structure to your data?

from datetime import datetime

max(datetime.strptime(''.join(x), '%I%M%p') for x in timeList)

# datetime.datetime(1900, 1, 1, 20, 23)
# i.e. 8.23pm

While you say "formatting of list should not be changed", that's exactly what all solutions are implicitly doing in order to perform comparisons.

like image 40
jpp Avatar answered Sep 27 '22 22:09

jpp


key param with the max function is used to notify max on which value you want to perform max operation. itemgetter(2) fetches the value at 2nd index, and lexicographically "PM" is the highest value in the list at index 2 (lexicographically 'PM' > 'AM'). You may use a lambda function to calculate the maximum on tuple at index 0 and 1 as:

>>> timeList = [('4', '12', 'PM'), ('8', '23', 'PM'), ('4', '03', 'AM'), ('1', '34', 'AM'), ('12', '48', 'PM'), ('4', '13', 'AM'), ('11', '09', 'AM'), ('3', '12', 'PM'), ('4', '10', 'PM')]

# type-casting it to `int` to avoid incorrect result 
# due lexicographical comparision of `str`
>>> max(timeList, key=lambda x: (x[2], int(x[0]), int(x[1])))
('12', '48', 'PM')            #   ^      ^         ^ Third priority to `int` value of minute
                              #   ^      ^ Second priority to int value of `hour`
                              #   ^ First priority to lexicographically sort on `AM`/`PM`

OR, you perform the comparison on datetime.datetime object as:

>>> from datetime import datetime

>>> max(timeList, key=lambda x: datetime.strptime('{}:{}{}'.format(*x), '%I:%M%p'))
('8', '23', 'PM')

I think you should have created the list of datetime.datetime instead of time tuples initially.

like image 24
Moinuddin Quadri Avatar answered Sep 27 '22 22:09

Moinuddin Quadri