Reputation: 6599
Given a month in numeric form (e.g., 2 for February), how do you find the first month of its respective quarter (e.g., 1 for January)?
I read through the datetime
module documentation and the Pandas documentation of their datetime functions, which ought to be relevant, but I could not find a function that solves this problem.
Essentially, what I am trying to understand is how I could produce a function like the one below that, given month x, outputs the number corresponding to the first month of x's quarter.
>> first_month_quarter(5)
4
Upvotes: 18
Views: 7014
Reputation: 24427
Packing a lookup table into a 64-bit literal:
# Python 2
def firstMonthOfQuarter(month):
return (0x000aaa7774441110L >> (month << 2)) & 15
# Python 3
def firstMonthOfQuarter(month):
return (0x000aaa7774441110 >> (month << 2)) & 15
Upvotes: 6
Reputation: 122091
It's not so pretty, but if speed is important a simple list lookup slaughters math
:
def quarter(month, quarters=[None, 1, 1, 1, 4, 4, 4,
7, 7, 7, 10, 10, 10]):
"""Return the first month of the quarter for a given month."""
return quarters[month]
A timeit
comparison suggests this is about twice as fast as TigerhawkT3's mathematical approach.
Test script:
import math
def quarter(month, quarters=[None, 1, 1, 1, 4, 4, 4,
7, 7, 7, 10, 10, 10]):
"""Return the first month of the quarter for a given month."""
return quarters[month]
def firstMonthInQuarter1(month):
return (month - 1) // 3 * 3 + 1
def firstMonthInQuarter2(month):
return month - (month - 1) % 3
def first_month_quarter(month):
return int(math.ceil(month / 3.)) * 3 - 2
if __name__ == '__main__':
from timeit import timeit
methods = ['quarter', 'firstMonthInQuarter1', 'firstMonthInQuarter2',
'first_month_quarter']
setup = 'from __main__ import {}'.format(','.join(methods))
results = {method: timeit('[{}(x) for x in range(1, 13)]'.format(method),
setup=setup)
for method in methods}
for method in methods:
print '{}:\t{}'.format(method, results[method])
Results:
quarter: 3.01457574242
firstMonthInQuarter1: 4.51578357209
firstMonthInQuarter2: 4.01768559763
first_month_quarter: 8.08281871176
Upvotes: 24
Reputation: 882028
It's a simple mapping function that needs to convert:
1 2 3 4 5 6 7 8 9 10 11 12
|
V
1 1 1 4 4 4 7 7 7 10 10 10
This can be done in a number of ways with integral calculations, two of which are:
def firstMonthInQuarter(month):
return (month - 1) // 3 * 3 + 1
and:
def firstMonthInQuarter(month):
return month - (month - 1) % 3
The first involves integer division of the month converted to a zero-based month to get the zero-based quarter, multiplication to turn that back into a zero-based month (but the month at the start of the quarter), then adding one again to make the range 1..12
.
month -1 //3 *3 +1
----- -- --- -- --
1 0 0 0 1
2 1 0 0 1
3 2 0 0 1
4 3 1 3 4
5 4 1 3 4
6 5 1 3 4
7 6 2 6 7
8 7 2 6 7
9 8 2 6 7
10 9 3 9 10
11 10 3 9 10
12 11 3 9 10
The second just subtracts the position within a quarter (0, 1, 2) from the month itself to get the starting month.
month(a) -1 %3(b) a-b
-------- -- ----- ---
1 0 0 1
2 1 1 1
3 2 2 1
4 3 0 4
5 4 1 4
6 5 2 4
7 6 0 7
8 7 1 7
9 8 2 7
10 9 0 10
11 10 1 10
12 11 2 10
Upvotes: 26
Reputation: 6599
Here is an answer suggested by TigerhawkT3. Perhaps the leanest suggestion so far and, apparently, also the fastest.
import math
def first_month_quarter(month):
return int(math.ceil(month / 3.)) * 3 - 2
For example:
>> first_month_quarter(5)
4
Upvotes: 10
Reputation: 168716
def first_month(month):
return (month-1)//3*3+1
for i in range(1,13):
print i, first_month(i)
Upvotes: 14