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.
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
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With